I have got a ViewPager which is populated by a FragmentPagerAdapter. I want to change from the first adapter two another. The problem is that all pages which were loaded before (while having the first adapter) are still the old ones.
I looked at the source code of FragmentPagerAdapter and guess the problem occurs because of the instatiateItem() implementation. Using the tag, which has position and conatiner id in it, the method checks if there is already a Fragment at the position. When there is a Fragment with this tag it is attached. The container id and position do not change when setting a new adapter so it finds the old Fragments.
Do you know a way to remove all old fragments?
I realized that the Fragment tag uses an id, obtained by getItemId() and not the position. To solve my issue I have overwritten getItemId() and use totally different ids in the different FragmentPagerAdapters.
#Override
public long getItemId(int position) {
return mPagerId*100+position;
}
mPagerId is an unique integer that is assigned in the constructor. If you have more than 100 pages you should replace 100 with 1000.
Related
My activity has a spinner and an empty container where fragments should be added when selecting drop down items from spinner.
My plan was to try to make switch construction inside into override method "public void onItemSelected()", where each case represents one drop down item from spinner, and sets correct fragment into container, like this:
String itemSelectedFromSpinner = parent.getSelectedItem().toString();
switch (itemSelectedFromSpinner) {
case "first item": // First and second item put same fragment into container, but do other methods when used
case "second item": // my code
}
My other taught was to put it in if construction like this:
String itemSelectedFromSpinner = parent.getSelectedItem().toString();
if (itemSelectedFromSpinner.equals("first item") || itemSelectedFromSpinner.equals("second item")){
// my code }
Since I've never done something like this, and I believe you can understand from my question what needs to be done, tell me what is the best practice to do that.
Am I doing it right by putting a String itemSelectedFromSpinner into switch construction? Also if user selects one item and first fragment is loaded, when selecting other item will the first fragment disappear and put second fragment into container automatically? (sorry if this sound little silly to you, I have lack of experience with fragments)
Don't use string like "first item" in code directly - move them to string resources.
For components like spinner use Adapter. The concept of Adapters use widely in Android so it's a good idea to be familiar with it. And also it allow you to compare your data by some integer asigned id's, and not by strings (which is unefficient, slow and ugly - correcting string representation everywhere is hard).
To replace or add Fragments dynamically use FragmentManager. See the simple replace() / add() / commit() code
After reading up hordes of SO answers, this challenge remains.
This is the relevant piece of code in a v4 FragmentStatePagerAdapter:
#Override
public int getCount()
{
int count = 2;
if (pref_dealsOnly)
count = 1;
return count;
}
Like you already guessed, it just crashes with the following exception:
java.lang.IllegalStateException: The application's PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged! Expected adapter item count: 2, found: 1 Pager id: com.droid.shopper:id/shopperMainPager Pager class: class android.support.v4.view.ViewPager Problematic adapter: class com.sndroid.globeshopper.shopper.ShopperJournal$JournalPagerAdapter
I fail to understand why the exception occurs.
Please help on how to accomplish what seems to be so simple and apparent - to return a getCount() value in a v4 FragmentStatePagerAdapter dynamically. Kindly let me know if more code is required.
Thanks in advance!
getCount() may be called several times by the ViewPager. It must remain constant, for the life of that PagerAdapter, for reliable results with FragmentPagerAdapter and FragmentStatePagerAdapter.
In theory, you could do what the error message tells you and call notifyDataSetChanged() on the PagerAdapter at the point when getCount() will start returning a different value. While that can work with a custom PagerAdapter implementation, neither FragmentPagerAdapter nor FragmentStatePagerAdapter used to handle it all that well. I wound up creating another PagerAdapter implementation to support adding and removing pages. It is possible that FragmentPagerAdapter and/or FragmentStatePagerAdapter are behaving better nowadays, though I doubt it.
In your case, it may be a matter of swapping in a different PagerAdapter at the point when pref_dealsOnly changes value.
I have created pages in Android by using PagerAdapter. When I tried to get value in the third page from EditText, I got NullPointerException.
In the third fragment page, I have initialled that EditText already.
iName = (EditText) view.findViewById(R.id.name);
So does this problem occur while the third page havenĀ“t initialled?
A ViewPager, by default, keeps one fragment on each side in memory. You can change this by setting the offscreen page limit.
From the documentation:
Set the number of pages that should be retained to either side of the
current page in the view hierarchy in an idle state. Pages beyond this
limit will be recreated from the adapter when needed.
This is offered as an optimization. If you know in advance the number
of pages you will need to support or have lazy-loading mechanisms in
place on your pages, tweaking this setting can have benefits in
perceived smoothness of paging animations and interaction. If you have
a small number of pages (3-4) that you can keep active all at once,
less time will be spent in layout for newly created view subtrees as
the user pages back and forth.
You should keep this limit low, especially if your pages have complex
layouts. This setting defaults to 1.
Parameters limit How many pages will be kept offscreen in an idle
state.
Example usage: yourViewPager.setOffscreenPageLimit(2);
When you use the default implementation of setOffscreenPageLimit() it is only loading the one fragment which is to the right of it. For eg. when you are on index 1, it has index 2 loaded in memory but not index 0, so swiping left will have to generate a new fragment from scratch. To test this theory you might want to use setOffscreenPageLimit(2) and then try swiping index 1->0. This in no way is the optimal solution but will help clear out your doubts about the concept.
you can store the fragment state of all the pages in the pages adaper like this and get reference of it.
PagerAdapter
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragmentsList;
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment=new ScreenSlidePageFragment();
fragmentsList.add(fragment, position);
return fragment;
}
#Override
public int getCount() {
return NUM_PAGES;
}
public static List<Fragment> getFragmentsList(){
return fragmentsList;
}
}
In Activity:
List<Fragment> fragList=adapter.getFragmentsList()
View view = fragList.get(position).getView();
if (view !=null) {
view.findViewById(R.id.text_view).setText("Child Accessed :D");
}
I have a ViewPager that I shows more than one item. I used the solution of putting multiple items in one fragment and I created an Adapter that calculates how many items I can put per fragment based in the width of the screen.
The getItem of my FragmentPagerAdapter creates a range of items for each fragment, dividing the quantity of items for each fragment.
For example, I have 12 items and in the portrait orientation, I can put 3 items. The getItem will create fragments with the range of 0-2, 3-5, 6-8 and 9-11. In the landscape orientation, since the width of the screen is bigger, I can put, for example, 5 items. So, the ranges would be 0-4, 5-8 and 9-11.
With this requirement, I need to create new Fragments and destroy the old ones on orientation changes.
I created a solution, but it depends on the method isChangingConfigurations() of the Activity. But this method just exists for API level 11 and above. So I can't use it.
Basically, I I'm not allowing the Fragment to save its state and I'm removing it in the onPause if the configuration is changing. But since I don't have this method in old android versions, I need another solution.
Can anyone help me?
Another solution is:
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
if (pagerAdapter != null) {
pagerAdapter.removeAllfragments();
}
super.onSaveInstanceState(savedInstanceState);
}
And the code for adapter:
public void removeAllfragments()
{
if ( mFragmentList != null ) {
for ( Fragment fragment : mFragmentList ) {
mFm.beginTransaction().remove(fragment).commit();
}
mFragmentList.clear();
notifyDataSetChanged();
}
}
mFragmentList should add fragments inside of:
#Override
public Fragment getItem(int position) {}
The easiest solution to your current approach is probably to ensure that getItem(int) returns different values for the landscape and portrait orientations. The id is used to generate the fragment tag, that, after an orientation change, is used to retrieve a detached fragment and reattach it. With different ids for the situation where respectively 3 items and 5 items are next to each other, a fragment with 3 items will never be reattached if there should be 5.
By default, getItemId(int) simply returns the supplied position of the element, like so:
public long getItemId(int position) {
return position;
}
In order to return different ids for your situations, you have various options. A straightforward one would be to do something smart with the indices of all items displayed for the given position. Alternatively, you could do a simple device orientation check and offset the id with the total number of items, or do something smart with a string and a hashcode etc. Just make sure you return the same id for the same fragment in the same orientation.
I'm not familiar with FragmentPagerAdapter, so this is going to be one of those questions that we (you) read the description critically.
Structure: I have a FragmentPagerAdapter (code below), that will hold two fragments at a time. The first displays book excerpts, and the second a list of book titles.
Goal: I want to achieve what is described in the title: the user can navigate to the second fragment in the pager, click on a title, and then I want to move the user back to the first fragment and tell the first fragment to update the text. The first fragment has a triggerRefresh method for that.
Code: I believe my problem happens because of the way FragmentPagerAdapter reuses/creates the Fragments (which I don't understand). This is my class:
static class MyFragmentPagerAdapter extends FragmentPagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
switch(position) {
case 0:
return new ExcerptsFragment();
case 1:
return new BookListFragment();
default:
throw new IllegalArgumentException("not this many fragments: " + position);
}
}
}
This is how I created the relevant members:
ViewPager mViewPager = (ViewPager) findViewById(R.id.pager);
MyFragmentPagerAdapter mFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mFragmentPagerAdapter);
And this is what I've tried elsewhere in my Activity, when I receive the callback from the book titles Fragment with the title selected:
mViewPager.setCurrentItem(0); // back to excerpts screen page. It's OK.
// Here's the problem! How to identify the fragment 0
// to ExcerptsFragment and call its triggerRefresh()?!?
Series of problems:
Calling the adapter's getView() won't work because it will return a new instance of ExcerptsFragment, which is not the one currently attached (as expected, throws the exception).
I've seen many people here (example) just storing fragments in the getView(). Is that right? Because by looking at the official examples, seems like an anti-pattern to me (defeat the automatic reference by holding the items). And that is also the opinion here and here (and looks right to me).
Any suggestions? I wouldn't be surprised if I'm not understanding all of this one bit...
Disclaimer: Although this had worked perfectly fine for me before, you should be aware of the classic pitfalls of depending on internal, private behavior. While I wrote tests that would eventually warn me if the internal implementation changed, I have since moved on to greener pastures. And you should, too. As such, the value of this question and its answer is only historical, in my opinion.
Sorry about that question, I think it was the hour.
To solve that problem, I implemented this solution as is. Seems to work just fine. So, I believe it was just a matter of finding the (currently attached) fragment instance by figuring out how its Id is named. The link above explains how it's made.
I opted to answer my own question instead of deleting it because I believe novices like me on these pagers will benefit from a "real case scenario". Most of the answers I've seen talk most about the theory, which is the right way BTW... but without a real example to work on sometimes people like me get lost.
Anyway, here is the last piece of code that I needed (the commented part above):
int n = 0;
mViewPager.setCurrentItem(n); // in the question I had stopped here.
ExcerptsFragment f = (ExcerptsFragment) ContainerActivity.this
.getSupportFragmentManager().findFragmentByTag(getFragmentTag(n));
f.triggerRefresh();
// ... below the helper method: used the solution from the link.
private String getFragmentTag(int pos){
return "android:switcher:"+R.id.pager+":"+pos;
}
So, I'm having a feeling that this is a robust solution, because I'm not holding any references to fragments (thus risking the references being outdated). I kept my own code at a minimum, therefore minimizing the chances of me doing something stupid.
Of course, if you have something to add, to show us, to tell what is wrong in doing it or what can be improved, I'll be glad to hear from you.
I searched for a solution to this problem a while myself. Your approach in principle works, but it will break your code if ever the code of the fragment tag creation in the Android base class implementation changes. This is a quite nasty dependency!
A more elegant approach would be to turn the problem around and keep an instance of your base activity in your fragment. Implement a setter for the tag in your activity and call that inside the fragment upon creation - the tag there is simply available with getTag().
An example implementation can be found here.
I solved this problem by using WeakReferences to the fragments upon creation. See : https://stackoverflow.com/a/23843743/734151
If you find anything wrong with this approach, please comment.