Removing fragments from FragmentStatePagerAdapter
Asked Answered
B

3

11

UPDATE

After some major fighting with this problem and help from SO users I managed to solve it. This is how my clearProgressUpTo looks like now.

    public void clearProgressUpTo(int progress) {
    boolean deleted = false;

    for (int i = fragments.size() - 1; i > 0; i--) {
        int key = fragments.keyAt(i);
        if (key != progress) {
            fragments.delete(key);
            deleted = true;
        } else {
            break;
        }
    }

    if (deleted)
        notifyDataSetChanged(); //prevents recursive call (and IllegalStateException: Recursive entry to executePendingTransactions)

    while (mSavedState.size() > progress) {
        mSavedState.remove(mSavedState.size()-1);
    }

}

The reason why I am doing it after notifyDataSetChanged is because the initiateItem was filling my mSavedState once again. And I am sure that after notifying my adapter does not hold any of these Fragments.

Also if you want to do it like that change your mSaveState to protected so you will be able to get it from the extending class (in my case VerticalAdapter).

I forgot to mention that I am using FragmentStatePagerAdapter fix by Adam Speakman to solve my problem.

/////////////////////////////////////////////////////QUESTION PART//////////////////////////////////////////////////////

the problem that I'm having is that I need to remove completely some of Fragments held in FragmentStatePagerAdapter.

I have already done the POSITION_NONE fix on it but the old fragments seems to still remain intact.

CONSTRUCTION

Because the construction of my ViewPager is unusual I was forced to place Inside my VerticalAdater extending FragmentStatePagerAdapter a SparseArray that holds all my fragments.

public class VerticalAdapter extends FragmentStatePagerAdapter {

private SparseArray<Fragment> fragments = new SparseArray<Fragment>(); 
...

@Override
public Fragment getItem(int position) {
    return getFragmentForPosition(position);
}

@Override
public int getCount() {
    return fragments.size();
}

PROBLEM

I have made a special method responsible for clearing all the Fragments up to some point in my FragmentStatePagerAdapter. Its working well but the problem I cant get rid of is clearing the ArrayList inside FragmentStatePagerAdapter. Even tho I clear the SparseArray, the ArrayList of FragmentStatePagerAdapter that my VerticalAdapter extends is still holding the Fragments that I don't want to have there. Because of that when I create new Fragments they have the state of the old ones.

public void clearProgressUpTo(int progress) {
    boolean deleted = false;

    for (int i = fragments.size() - 1; i > 0; i--) {
        int key = fragments.keyAt(i);
        if (key != progress) {
            fragments.delete(key);
            deleted = true;
        } else {
            break;
        }
    }

    if (deleted)
        notifyDataSetChanged(); //prevents recursive call (and IllegalStateException: Recursive entry to executePendingTransactions)
}

If you need more info/code please tell me in the comments I will try to provide as much info as I can.

I have no idea how to fix this problem so any input would be appreciated.

EDIT

Adding requested code.

private Fragment getFragmentForPosition(int position) {
    return fragments.get(getKeyForPosition(position));
}

public int getKeyForPosition(int position) {
    int key = 0;
    for (int i = 0; i < fragments.size(); i++) {
        key = fragments.keyAt(i);
        if (i == position) {
            return key;
        }
    }
    return key;
}
Belovo answered 19/9, 2014 at 7:36 Comment(5)
post getFragmentForPosition(position); please.Unlikely
Added the code you requested.Belovo
The only thing that's missing here from my Adatper is method setProgress that fills the fragments array. Why I know its old state? Because setProgress is reloading whole fragments array with newinstance of my Fragments. Also the ids of Fragments inside the ViewPager array are the same as they were before setProgress.Belovo
I think my problem is associated with #9727673 Maybe because I hold my Fragments in SparseArray is the reason why notifyDataSetChanged is not updating my data.Belovo
Is this bug fixed in the Viewpager2 state pager adapter? Could anyone please confirm?Partida
N
7

FragmentStatePageAdapter saves the state of the fragment while destroying it, so that latter when the fragment is recreated the saved state will be applied to it.

The fragment state is stored in a private member variable. Hence we can't extend the current implementation to add the feature of removing the saved state.

I've cloned the current FragmentStatePagerAdapter implementation and have included the saved state removal functionality. Here is the gist reference.

   /**
     * Clear the saved state for the given fragment position.
     */
    public void removeSavedState(int position) {
        if(position < mSavedState.size()) {
            mSavedState.set(position, null);
        }
    }

In your clearProgressUpTo method, whenever you remove the fragment from sparse array, call this method, which clears off the saved state for that fragment.

Nancynandor answered 24/9, 2014 at 1:44 Comment(2)
I solved my problem yesterday and its connected to your answer. I did take mSaveState from FragmentStatePagerAdapter and used mSaveState.remove on the elements that I am getting rid off. Going to accept your answer because its the closest to fixing this problem.Belovo
Hi JakubW... could you share your answerAruwimi
D
7

Your problem is not caused by your use of SparseArray. In fact, you meet a bug of the FragmentStatePagerAdapter. I spent some time reading the source code of FragmentStatePagerAdapter, ViewPager and FragmentManagerImpl and then found the reason why your new fragments have the state of old ones:

The FragmentStatePagerAdapter save the sate of your old fragments when it remove them, and if you add new fragments to you adapter later, the saved state will be passed to your new fragment at the same location.

I googled this problem and find a solution, here is its link: http://speakman.net.nz/blog/2014/02/20/a-bug-in-and-a-fix-for-the-way-fragmentstatepageradapter-handles-fragment-restoration/

The source code can be found here: https://github.com/adamsp/FragmentStatePagerIssueExample/blob/master/app/src/main/java/com/example/fragmentstatepagerissueexample/app/FixedFragmentStatePagerAdapter.java

The key idea of this solution is to rewrite the FragmentStatePagerAdapter and use a String tag to identify each fragment in the adapter. Because each fragment has a different tag, it is easy to identify a new fragment, then don't pass a saved state to a new Fragment.

Finally, say thanks to Adam Speakman who is the author of that solution.

Definiendum answered 22/9, 2014 at 12:0 Comment(10)
I have used this bug fix before posting on SO and it did not fix the problem :C I am going to give it another shot tho and post my results.Belovo
Maybe I misunderstand your problem, I will check your post and make it clear after you solve it.Definiendum
I am trying to use it but for now the problem still occurs. However it seems to fix something else for me. But there is another problem with this solution. When I extend my fragments from { 1, 2, 3, 7 } into { 1, 2, 3, 4, 7 } the TAG is not inserted into new Fragment (4). The wierd thing is that it does work after screen rotation.Belovo
@Belovo I don't understand your new problem. Is there anything wrong when you insert a new Fragment(4) ? Or, nothing wrong, but there is a problem after you rotate your screen, what's it?Definiendum
There is a problem after inserting new Fragment(4) the TAG is not inserted into it. IT WORKS only after rotation of screen occurs.Belovo
Ok I fixed this one with POSITION_NONE. It wasn't used in bugfix but it looks like its required in my situation. Worst thing is that my original problem is still here and I cannot get rid of it :CBelovo
@Belovo Yeah, you need POSITION_NONE in your case because your Fragments' position may be changed.Definiendum
How come it still have my old state even after I recreate the Fragments inside my Adapter?Belovo
I did some more testing: After using clearProgressUpTo which as u can see in my post is responsible for deleting all Fragments up to some position. But the thing is the state of these Fragments is still in memory even after notifyDataSetChanged. Is there a way to get rid of them with all additional info? So the result will be: 2 first Fragments have the old data but all the rest are removed and on creation will have a clean slate.Belovo
Let us continue this discussion in chat.Definiendum
N
7

FragmentStatePageAdapter saves the state of the fragment while destroying it, so that latter when the fragment is recreated the saved state will be applied to it.

The fragment state is stored in a private member variable. Hence we can't extend the current implementation to add the feature of removing the saved state.

I've cloned the current FragmentStatePagerAdapter implementation and have included the saved state removal functionality. Here is the gist reference.

   /**
     * Clear the saved state for the given fragment position.
     */
    public void removeSavedState(int position) {
        if(position < mSavedState.size()) {
            mSavedState.set(position, null);
        }
    }

In your clearProgressUpTo method, whenever you remove the fragment from sparse array, call this method, which clears off the saved state for that fragment.

Nancynandor answered 24/9, 2014 at 1:44 Comment(2)
I solved my problem yesterday and its connected to your answer. I did take mSaveState from FragmentStatePagerAdapter and used mSaveState.remove on the elements that I am getting rid off. Going to accept your answer because its the closest to fixing this problem.Belovo
Hi JakubW... could you share your answerAruwimi
L
0

When you call notifyDataSetChanged() the adapter starts removing its fragments using destroyItem(ViewGroup container, int position, Object object).

However, each fragment's instance state is retained internally by FragmentStatePagerAdapter to be later used in instantiateItem(ViewGroup container, int position).

Simply save item's position you want to remove and call notifyDataSetChanged(). Just after the last item is destroyed, retrieve the desired saved instance state from FragmentStatePagerAdapter and replace with null.

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);

    if(position == getCount() - 1 && i >= 0){
        Bundle state = (Bundle) saveState();
        Fragment.SavedState[] states = (Fragment.SavedState[]) state.getParcelableArray("states");
        if (states != null)
            states[i] = null;
        i = -1;
        restoreState(state, ClassLoader.getSystemClassLoader());
    }

Note: getItemPosition(Object object) must return PagerAdapter.POSITION_NONE.

Lieutenancy answered 28/8, 2018 at 11:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.