Skip to content

Instantly share code, notes, and snippets.

@carlonzo
Created August 2, 2015 17:08
Show Gist options
  • Save carlonzo/a6fc852b6167ae6c30ee to your computer and use it in GitHub Desktop.
Save carlonzo/a6fc852b6167ae6c30ee to your computer and use it in GitHub Desktop.
FragmentStatePagerAdapter that caches each pages.
/*
* Copyright 2014 Soichiro Kashima
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.ksoichiro.android.observablescrollview;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.util.SparseArray;
import android.view.ViewGroup;
/**
* FragmentStatePagerAdapter that caches each pages.
* FragmentStatePagerAdapter is also originally caches pages,
* but its keys are not public nor documented, so depending
* on how it create cache key is dangerous.
* This adapter caches pages by itself and provide getter method to the cache.
*/
public abstract class CacheFragmentStatePagerAdapter extends FragmentStatePagerAdapter {
private static final String STATE_SUPER_STATE = "superState";
private static final String STATE_PAGES = "pages";
private static final String STATE_PAGE_INDEX_PREFIX = "pageIndex:";
private static final String STATE_PAGE_KEY_PREFIX = "page:";
private FragmentManager mFm;
private SparseArray<Fragment> mPages;
public CacheFragmentStatePagerAdapter(FragmentManager fm) {
super(fm);
mPages = new SparseArray<Fragment>();
mFm = fm;
}
@Override
public Parcelable saveState() {
Parcelable p = super.saveState();
Bundle bundle = new Bundle();
bundle.putParcelable(STATE_SUPER_STATE, p);
bundle.putInt(STATE_PAGES, mPages.size());
if (0 < mPages.size()) {
for (int i = 0; i < mPages.size(); i++) {
int position = mPages.keyAt(i);
bundle.putInt(createCacheIndex(i), position);
Fragment f = mPages.get(position);
mFm.putFragment(bundle, createCacheKey(position), f);
}
}
return bundle;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
Bundle bundle = (Bundle) state;
int pages = bundle.getInt(STATE_PAGES);
if (0 < pages) {
for (int i = 0; i < pages; i++) {
int position = bundle.getInt(createCacheIndex(i));
Fragment f = mFm.getFragment(bundle, createCacheKey(position));
mPages.put(position, f);
}
}
Parcelable p = bundle.getParcelable(STATE_SUPER_STATE);
super.restoreState(p, loader);
}
/**
* Get a new Fragment instance.
* Each fragments are automatically cached in this method,
* so you don't have to do it by yourself.
* If you want to implement instantiation of Fragments,
* you should override {@link #createItem(int)} instead.
*
* {@inheritDoc}
*
* @param position position of the item in the adapter
* @return fragment instance
*/
@Override
public Fragment getItem(int position) {
Fragment f = createItem(position);
// We should cache fragments manually to access to them later
mPages.put(position, f);
return f;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (0 <= mPages.indexOfKey(position)) {
mPages.remove(position);
}
super.destroyItem(container, position, object);
}
/**
* Get the item at the specified position in the adapter.
*
* @param position position of the item in the adapter
* @return fragment instance
*/
public Fragment getItemAt(int position) {
return mPages.get(position);
}
/**
* Create a new Fragment instance.
* This is called inside {@link #getItem(int)}.
*
* @param position position of the item in the adapter
* @return fragment instance
*/
protected abstract Fragment createItem(int position);
/**
* Create an index string for caching Fragment pages.
*
* @param index index of the item in the adapter
* @return key string for caching Fragment pages
*/
protected String createCacheIndex(int index) {
return STATE_PAGE_INDEX_PREFIX + index;
}
/**
* Create a key string for caching Fragment pages.
*
* @param position position of the item in the adapter
* @return key string for caching Fragment pages
*/
protected String createCacheKey(int position) {
return STATE_PAGE_KEY_PREFIX + position;
}
}
@shivasurya
Copy link

this implementation is really good and useful - but slight modification in 72 on restoreinstance where the old mPage resides in memory seems and contains old value.clear the mPage(mPage.clear() ) and start setting the fragment by getFragment()
this solved issue when i changed orientation between portrait and landscape.

@Kolyall
Copy link

Kolyall commented May 17, 2019

The error occurs:

java.lang.IllegalStateException: Fragment PlacesMapFragment{e264ad0 (36b68aeb-efd5-4d60-8dd1-65368a752a90)} is not currently in the FragmentManager
        at androidx.fragment.app.FragmentManagerImpl.putFragment(FragmentManagerImpl.java:350)
        at com.example.CacheFragmentStatePagerAdapter.saveState(CacheFragmentStatePagerAdapter.java:47)
        at androidx.viewpager.widget.ViewPager.onSaveInstanceState(ViewPager.java:1445)
        at android.view.View.dispatchSaveInstanceState(View.java:18792)
        at android.view.ViewGroup.dispatchSaveInstanceState(ViewGroup.java:3905)
        at android.view.ViewGroup.dispatchSaveInstanceState(ViewGroup.java:3911)
        at android.view.View.saveHierarchyState(View.java:18775)
        at androidx.fragment.app.FragmentManagerImpl.saveFragmentViewState(FragmentManagerImpl.java:2263)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:944)
        at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1228)
        at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:434)
        at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2066)
        at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1856)
        at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1811)
        at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1717)
        at androidx.fragment.app.FragmentManagerImpl$2.run(FragmentManagerImpl.java:150)
        at android.os.Handler.handleCallback(Handler.java:789)
        at android.os.Handler.dispatchMessage(Handler.java:98)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6944)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment