I have an ExpandableListView that loads well the first time but then after the adapter values are changed and notifyDataSetChanged called (I even tried setting the adapter again) the ExpandableListView on screen stays the same... however getCount() does change. Before posting the code, let me explain my intention and what I've tried so far. I want to generate the data for the adapter's variables when page 2 in the ViewPager is shown, and then show it in the ExpandableListView inside that same fragment. I managed to do this in several different ways but there's always a problem that makes me look for an alternative.
First I made all of this in the ViewPager's default class, MainActivity, using addOnPageChangeListener instead of SimpleOnPageChangeListener, and made the variables related to the ExpandableListView static, so I could set the adapter from MainActivity. The data was generated flawlessly everytime the specific page was shown, but there was a problem: the ExpandableListView wasn't showing anything, even tho all the variables involved had the correct values and the adapter was set.
So I took another road... I made the ViewPager static, put the listener for it in page 2 fragment class and generated data+set adapter from there, no static variables involved with the exception of the ViewPager. This works flawlessly for the first swipe to the page, data is generated and shown successfully, but in subsequent swipes to the page something weird happens: the code inside the listener is called x times each time, with x being the number of times the user has swiped to the fragment. This means first time it calls the code once (good), then two times second time and so on... this obviously messes up everything. I tested and this doesn't happen when the listener is in MainActivity, the code there is called only once everytime (but ExpandableListView is not showing anything). In this stage first swipe is perfect but second also delivers the expected result, updating the ExpandableListView and everything but doing so much work than necessary (because of running the code once), then at third swipe I get a NullPointerException (no surprise given the amount of messing it does).
At this point I get very frustrated, it seems everytime I use a static variable it doesn't work correctly: first with the ExpandableListView related variables used to set the adapter from MainActivity, then with making the ViewPager static and using a listener from it to run a method in another class.
Next step was reading for a substitute to addOnPageChangeListener to see if that worked, that's when I met SimpleOnPageChangeListener. I was happy, it worked... it run the code only once with every swipe to the Page while also showing the result in the ExpandableListView but, problem... it doesn't update it even tho it's data changes successfully (current stage).
So I'm kind of new with programming in general and don't have a clue of what's going on here... if it's that I'm discovering a bunch of bugs, something messed up with my installed SDK or if there's something else I'm missing. Please help.
Simplified version of my current stage:
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class ResultsFragment extends android.support.v4.app.Fragment {
ExpandableListView resultsELV;
HashMap<Header,List<String>> resultsHash = new HashMap<Header, List<String>>();
List<Header> resultsTitles = new ArrayList<>();
ResultsAdapter resultsAdapter;
View rootView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_results, container, false);
resultsELV = (ExpandableListView)rootView.findViewById(R.id.expandableListVieww);
ViewPager.SimpleOnPageChangeListener listener = new
ViewPager.SimpleOnPageChangeListener(){
#Override
public void onPageSelected(int position) {
if (position == 1) {
getResults();
}
}
};
MainActivity.mViewPager.setOnPageChangeListener(listener);
return rootView; }
public void getResults() {
// resultsHash & resultsTitles get filled
Toast.makeText(resultsELV.getContext(),resultsHash.size()+"
"+resultsTitles,Toast.LENGTH_SHORT).show();
resultsAdapter=new ResultsAdapter(resultsELV.getContext(),resultsHash,resultsTitles);
resultsELV.setAdapter(resultsAdapter);
}
}
Related
I have an activity with tabs, in each tab I load the same fragment to show different lists of data.
Although it's the same fragment and therefor the adapter is the same, and the information is loaded in the same way one of the fragment's RecyclerView doesn't show the information.
I know the data is getting there because I debugged it and also because otherwise it would be showing a text in the screen saying there is no data to show.
I have 4 or 5 tabs, the first two always load just fine. But on the others it seems like the onCreateView isn't completely executed.
This is called from the onCreateView.
if (offers.size() != 0)
noOffers.setVisibility(View.GONE); //this gets execute, it hide some text that otherwise would be on the left image
else
content.setVisibility(View.GONE);
this.offers = offers;
MyOffersAdapter adapter = new MyOffersAdapter(getActivity(), this.offers, user);
rvOffers.setAdapter(adapter);
rvOffers.setLayoutManager(new LinearLayoutManager(getActivity())); //this part is getting executed in all the tabs
Although it reaches the last line neither onCreateViewHolder nor onBindViewHolderof the adapter get execute for the tabs beyond the second one.
This is the code I'm using to create the after setting the tabs:
viewPager.setAdapter(adapter);
tabs.setupWithViewPager(viewPager);
viewPager.setOffscreenPageLimit(5);
tabs.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
}
});
Is there a way I can force those methods to be executed when the tab is clicked? Or what am I configuring wrong?
I'm a beginner in Android, so I apologize for the mistakes and I'd appreciate any constructive criticism.
I'm writing a basic application with a ListView of images, and when the user clicks on an item in the list, I want to display that image in a ViewPager, where the user can swipe back and forth to browse the whole list of images. Afterwards when the user presses the back button, I want to switch back to the ListView.
I manage the business logic in the MainActivity, which uses MainActivityFragment for the ListView and ImageHolderFragment for ViewPager.
The simplified code so far is as follows:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListItems = new ArrayList<>();
mListItemAdapter = new ListItemAdapter(this, R.layout.list_item, R.id.list_item_name, mListItems);
mListView = (ListView) findViewById(R.id.list_view_content);
mListView.setAdapter(mListItemAdapter);
mDeletedListItems = new ArrayList<>();
mViewPager = (ViewPager) getLayoutInflater().inflate(R.layout.image_display, null, true);
mImageAdapter = new ImageAdapter(getSupportFragmentManager(), mListItems);
mViewPager.setAdapter(mImageAdapter);
mViewPager.setOffscreenPageLimit(3);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mViewPager.setCurrentItem(position);
setContentView(mViewPager); // TODO: this is very wrong!
}
});
loadImages();
noContentText = (TextView) findViewById(R.id.no_content_text);
if (mListItems.isEmpty()) {
noContentText.setText(R.string.no_images);
} else {
mImageAdapter.notifyDataSetChanged();
}
}
Although this does work to some extent, meaning that it manages to display the ViewPager when an item in the list is clicked, there are two things about it ringing the alarm bells:
I've read that calling setContentView() for the second time in the same class is pretty much a sin. Nobody explained me why.
The back button doesn't work in this case. When it's pressed, the application is terminated instead of going back to the list view. I believe this is connected to the first point.
I would appreciate any help, explanations if my idea is completely wrong, and if my case is hopeless, I'd like to see a successful combination of ListView and ViewPager with transitions between each other.
Your activity already has R.layout.activity_main set as content view, which rightly displays the list view - that's what the responsibility of this activity is as you defined it. If we want to change what's shown on the screen, we should use a different instance of a building block (activity or fragment) to display the view pager images.
To say the least, imagine if you wanted to change the view to a third piece of functionality or UI, or a fourth... it would be a nightmare to maintain, extend and test as you're not separating functionality into manageable units. Fields that are needed in one view are mixed with those needed in another, your class file would grow larger and larger as each view brings its click listeners, callbacks, etc., you'd also have to override the back button so it does what you want - it's just not how the Android framework was designed to help you. And what if you wanted to re-use UI components in different contexts whilst tapping in to the framework's activity lifecycle callbacks? That's why fragments were introduced.
In your case, the list view could continue to run in your MainActivity and in your click listener, onItemClick you could start a new activity that will hold a viewPager:
Intent i = new Intent(MainActivity.this, MyLargePhotoActivityPager.class);
i.putExtra(KEY_POSITION, position);
// pass the data too
startActivityForResult(i, REQUEST_CODE);
Notice how you could pass the position to this activity as an int extra, in order for that second activity to nicely set the viewPager to the position that the user clicked on. I'll let you discover how to build the second activity and put the ViewPager there. You also get back button functionality assuming your launch modes are set accordingly, if needed. One thing to note is that when you do come back to the list View, you'd probably want to scroll to the position from the view pager, which is why you could supply that back as a result via a request code. The returned position can be supplied back to the list view.
Alternatively, you could use the same activity but have two fragments (see the link further above) and have an equivalent outcome. In fact, one of your fragments could store the list view, and the second fragment could be a fullscreen DialogFragment that stores a viewPager, like a photo gallery (some details here).
Hope this helps.
I've read that calling setContentView() for the second time in the
same class is pretty much a sin. Nobody explained me why.
Well, you kind of get an idea as to why.
When you use setContentView() to display another 'screen' you do no have a proper back stack.
You also keep references to Views (like mListView) that are not visible anymore and are therefore kind of 'useless' after you setContentView() for the second time.
Also keep in mind orientation changes or your app going to the background - you'll have to keep track of the state that your Activity was in which is way more complicated than it has to be if you have one Activity that does two different things.
You won't be arrested for doing things like you do right now, but it's just harder to debug and keep bug free.
I'd suggest using two different Activities for the two different things that you want to do, or use one Activity and two Fragments, swapping them back and forth.
If you insist on having it all in one Activity you need to override onBackPressed() (called when the user presses the back button) and restore the first state of your Activity (setContentView() again, pretty much starting all over).
I have an odd issue which I've never seen anywhere on SO so I've resorted to posting here, hoping I make it clear enough.
I have a simple SherlockFragmentActivity as shown further down which contains three fragments which all call getActivity().setTitle() in their onCreateOptionsMenu() allowing my app to change titles depending on which fragment is visible.
This works as desired, but for some reason (perhaps unrelated) when I exit my application by means of the HOME button occasionally the title isn't visible upon reopening the application. It seems that should I close my app and reopen it, it's fine but after leaving it for a while the title won't be there when I reopen it.
I have absolutely no idea what could be causing this so any help is appreciated. The layout of my application (relevant to this issue) is a basic splash screen (as an activity) with a loading bar which then opens the following FragmentActivity:
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import static java.lang.Math.*;
public class FragmentControl extends SherlockFragmentActivity {
private static final int NUM_PAGES = 3;
private ViewPager mPager;
private PagerAdapter mPagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_control);
ActionBar action = getSupportActionBar();
action.setDisplayShowTitleEnabled(true);
action.setDisplayShowHomeEnabled(false);
mPager = (ViewPager)findViewById(R.id.pager);
mPagerAdapter = new FragmentControlAdapter(getSupportFragmentManager(), NUM_PAGES);
mPager.setAdapter(mPagerAdapter);
// If this activity wasn't called after a reload
if((Integer)getIntent().getExtras().get("current") == null){
// Always start on the middle page, or as close as possible
mPager.setCurrentItem((int) ceil(NUM_PAGES/2));
// Otherwise start on the page we left for a smoother experience
} else {
mPager.setCurrentItem((Integer)getIntent().getExtras().get("current"));
}
}
}
Only when the application re-opens to the FragmentActivity do I see this issue, when reopening on anything else and navigating to this activity it's fine (as you'd expect).
Any and all help is appreciated, hope I've made it clear.
Oh, and if it matters I'm currently targeting API 17 with minimum support for API 8. The test phone I'm seeing this issue on is a HTC One S - not sure on other devices yet but I'm going to start looking.
occasionally (...) after leaving it for a while
This sounds like your application process is killed in the meantime.
Make sure to save instance state (like what title is displayed) using onSaveInstanceState and restore it in Activity.onCreate or Fragment.onViewCreated.
I really can't make this up so I'd be thankful for any hint. I must make some mistake here (4.1.2).
I have an Activity which, in onCreate(), sets up a subclassed ArrayAdapter for ListView items which render as a Checkable ViewGroup.
The Activity already utilizes a NonConfiguration mechanism to re-build the Adapter upon orientation change. However, it's currently not storing the ListView's getCheckedItemPosition() because I feel it shouldn't be necessary (details below).
Interestingly, what I'm observing is the following.
The Activity is rendered.
The user checks a ListView item.
The ListView item is displayed in a checked state.
The onItemClickListener calls getCheckedItemPosition() for the ListView and gets a correct result.
The user changes the screen orientation.
onCreate() re-builds the 'ListView' and it displays just like before (after onCreate(); see below).
onCreate() calls getCheckedItemPosition() and gets -1 despite the ListView showing the correcly checked item
Upon further examination, the following details emerge.
onCreate():
get ListView resource
build MyAdapter
set MyAdapter as adapter for ListView
getCheckedItemPosition() returns -1
after onCreate():
MyAdapter.getView() is being called
CheckableViewGroup.setChecked() is called with the correct checked value
the last two steps will be repeated for all items
As said before, I'm relying on the Android feature that View objects save their state if they have an ID assigned. I'd say this is the case since there must be some object out there which sets the correct checked status for all the list entries. (By the way, CheckableViewGroup also overrides on{Save,Restore}InstanceState() but that won't be called regardless whether or not it has an ID assigned (presumably because it never gets attached to the layout root?).
So it looks as if the ListView at the same time knows and does not know its getCheckedItemPosition()? Or am I on the wrong track?
public final class MyActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
itemList = (ListView)findViewById(R.id.ma_list);
listAdapter = new MyAdapter();
itemList.setAdapter(listAdapter);
itemList.setOnItemClickListener(
new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> av, View v, int pos, long id) { checkForm(); }
}
);
listAdapterSetup();
// this is where itemList.getCheckedItemPosition() returns -1
// when the listView has been re-built from NonConfiguration data:
checkForm();
}
}
I was making another test after I posted my question, because I was getting an idea while describing my observations.
And indeed, my suspicion was confirmed.
Timing is key here.
The ListView will report the correct getCheckedItemPosition() value in onResume() (but not before).
So the solution is easy: Perform any evaluation logic in onResume().
For instance, the mainActivity.java file is really clustered and to keep it clean i created a second .java(class) where i will execute some code upon a button press. I cannot figure out how to do it at all. And i am not sure what search terms to use either so i apologize if this has been covered.
Heres what i have in my "test" application.
I have a main activity with a single button on it.
package com.test.secondclass;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity {
Button startButton;
final Intent second = new Intent(getApplicationContext(), testClass.class);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startButton = (Button)findViewById(R.id.button1);
startButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
startActivity(second);
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
Now here is the "second" class that i made, now remember this is very short i am just using this for an exercise program before implementing it into my actual program.
package com.test.secondclass;
import android.app.Activity;
import android.widget.Toast;
public class testClass extends Activity{
public void onCreate(){
Toast.makeText(getApplicationContext(), "Second class thinger started", Toast.LENGTH_LONG).show();
}
}
And if i try this i get a force close immediately. If i comment out the "intent" part at the very beginning of my main activity then the program runs. But it doesnt do what i want. obviously. Thanks everyone
Add an OnClickListener to send whatever information to testClass, as shown below (untested):
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent secondIntent = new Intent();
secondIntent.setClassName(myPackageName, "testClass");
startActivityForResult(secondIntent, REQUEST_CODE);
});
References here and here.
Here's my viewpoint: you are receiving a forced close due the fact that you is trying to open user interface methods (such the Toast) with no context.
IMPORTANT: I'm assuming that you already defined both classes in the manifest.xml file!
Before explaining, I'll make a brief:
Toast: this class opens a quick message, receiving as main arguments the context, the message and the time-to-show;
The context: it is the "environment" where to show. Something like a visual scope, that defines the resources you have. In most cases, you can setup it with setContentView method.
Issue Explanation, in my opinion: The "crash" occurs because you opens the Toast message with no context. An activity is a UI control very similar to a viewpage. If you call a new activity, its very like to call a new page, and so, a new context. In the seccond activity, I haven't see any context. I think that you was assuming that the context is preserved from the first activity, but it ins't because its a new activity.
How to fix:
In the seccond class, define a layout view with setContentView, or...
Reimplement your seccond class as a Service, and call it through startService, or...
Define an AIDL mechams (similar to previous fix, but more sophisticated and complex, as it enables async method calls).
Hope it has helped in some way.
Thank you ALL for the answers!! I was actually able to do what i wanted by using the "stopSelf()" command after i displayed the Toast message. I implemented a service class and when i press the button the testClass.java class gets called and runs the "toast" message then immediately exits by the "stopSelf()" command. I made sure of this by including an "onDestroy()" method which also displayed a simple toast message confirming that the service was stopping :). I usually do stuff like this using threads but it was making the main activity really messy no matter how much formatting i did. So i wanted to have a seperate class i could use.
And to the commenter EfEs, i come from programming in C# language for windows. Android is a new playground for me and im still learning. i think im doing quite well but wasnt sure how to do what i asked. But i figured it out then. And thanks for clearing up that an Activity in android is like a "WindowsForm" in C# where it is completely new GUI for the user. I didnt know that. But thanks to all for helping me with your posts!