I am trying to implement saving and restoring state, but I am running into problems when replacing the main Fragment with a PreferenceFragment and then hitting the back button. My main Fragment consists of a ViewPager with a FragmentPagerAdapter with 3 Fragments to swipe through. None of the Fragment.onCreateView() callbacks for my 3 Fragments are invoked after hitting the back button. I have tried all the solutions I have come over here on SO, but I have not been able to resolve the issue.
Another possibly important thing to note is that the data for my 3 ViewPager Fragments are stored in separate classes which are instanced and accessible through the Activity. The 3 Fragments all contain RecyclerViews for listing a fair amount of data. This is done for cleanliness, but also so that these data persist in the Activity. This is probably not an issue, since it works fine when starting the app, but also since the main issue is that my Fragments are not recreated.
Unexpected behavior:
On application creation, everything works fine, but when I replace my main Fragment (containing a ViewPager with a FragmentPagerAdapter) with another one and then pressing the back button, the Fragments in the ViewPager are not recreated. The onCreateView() of my main Fragment is called.
Questions:
What am I missing? Is there some other callbacks that should be created? Where and how should I recreate the Fragments?
What have I tried:
FragmentAdapter in use, but I should really use getSupportFragmentManager() as was the solution here.EDIT: Use of erroneous FragmentManager actually was the source of my troubles. See my answer below.
@Override public int getItemPosition(Object object) {return POSITION_NONE;} to the FragmentPagerAdapter, as suggested here.FragmentPagerAdapter to FragmentStatePagerAdapter which should not matter here, as far as I understand. The internals of FragmentStatePagerAdapter actually keeps the Fragments, but they are not rendered anew.Fragment back using a FragmentTransaction when its onCreateView() is called and in several of the other callbacks of that Fragment, see MMMainFragment below.public class MMMainFragment extends Fragment { private MMViewPager mMMViewPager = null; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Note that this method IS called when the back button is pressed. // I have tried setting the content back to this instance in several places. View view = inflater.inflate(R.layout.mm_main_fragment, container, false); // Setup ViewPager. mMMViewPager = (MMViewPager) view.findViewById(R.id.mm_pager); TabLayout tabLayout = (TabLayout) view.findViewById(R.id.mm_tablayout); tabLayout.setupWithViewPager(mMMViewPager); return view; } public MMViewPager getMMViewPager() { return mMMViewPager; } } <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <android.support.design.widget.TabLayout android:id="@+id/mm_tablayout" android:layout_width="match_parent" android:layout_height="wrap_content" app:tabMode="scrollable" /> <com.mycompany.myapp.gui.mmpager.MMViewPager android:id="@+id/mm_pager" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" /> </LinearLayout> Then, in Activity.onCreate() i simply do:
// Insert Main Fragment. FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.mm_content_frame, new MMMainFragment()) .commit(); where mm_content_frame is a FrameLayout where I replace and remove Fragments. Then, when a button to view the preferences are pushed, I run the following snippet below in the Activity. I add this Fragment to the backstack to be able to use the back button.
public void showSettingsFragment() { getSupportActionBar().setTitle(getString(R.string.mm_drawer_settings)); getSupportFragmentManager().beginTransaction() .replace(R.id.mm_content_frame, new MMPreferencesFragment(), FragmentConstants.SETTINGS_FRAGMENT_TAG) .addToBackStack(null) .commit(); } public class MMViewPager extends ViewPager { private MMActivity mMMActivity; private MMPagerAdapter mMMPagerAdapter; public MMViewPager(Context context, AttributeSet attrs) { super(context, attrs); mMMActivity = (MMActivity) context; mMMPagerAdapter = new MMPagerAdapter(mMMActivity.getSupportFragmentManager(), mMMActivity); this.setOffscreenPageLimit(PagerConstants.OFFSCREEN_PAGE_LIMIT); this.setAdapter(mMMMPagerAdapter); this.setCurrentItem(PagerConstants.PAGE_FILTER_RECIPES); } } FragmentStatePagerAdapter:public class MMPagerAdapter extends FragmentStatePagerAdapter { private MMActivity mMMActivity; public MMPagerAdapter(FragmentManager fragmentManager, MMActivity mmActivity) { super(fragmentManager); mMMActivity = mmActivity; } @Override public Fragment getItem(int position) { switch (position) { case PagerConstants.PAGE_FILTER_RECIPES: return new FilteredRecipesFragment(); case PagerConstants.PAGE_SELECTED_RECIPES: return new SelectedRecipesFragment(); case PagerConstants.PAGE_SHOPPING_LIST: return new ShoppingListFragment(); default: return null; } } @Override public int getCount() { return PagerConstants.NUMBER_OF_PAGES; // 3 } @Override public CharSequence getPageTitle(int position) { switch (position) { case PagerConstants.PAGE_FILTER_RECIPES: return mMMActivity.getResources().getString(R.string.mm_title_recipe_filter_fragment); case PagerConstants.PAGE_SELECTED_RECIPES: return mMMActivity.getResources().getString(R.string.mm_title_selected_recipes_fragment); case PagerConstants.PAGE_SHOPPING_LIST: return mMMActivity.getResources().getString(R.string.mm_title_shopping_list_fragment); default: return "Tab"; } } } Activity:<?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/mm_drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- Source: http://developer.android.com/training/implementing-navigation/nav-drawer.html --> <!-- The main content view --> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <!-- Source: http://android-developers.blogspot.in/2014/10/appcompat-v21-material-design-for-pre.html --> <android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/mm_toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="?attr/actionBarSize" android:layout_alignParentTop="true" style="@style/MMActionBar" app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" /> <!-- The FrameLayout where I replace Fragments --> <FrameLayout android:id="@+id/mm_content_frame" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@id/mm_toolbar"/> </RelativeLayout> <ListView android:id="@+id/mm_drawer_listview" android:layout_width="260dp" android:layout_height="match_parent" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingStart="16dp" android:layout_gravity="start" android:choiceMode="singleChoice" android:divider="@android:color/transparent" android:dividerHeight="0dp" android:background="@color/mm_white" /> </android.support.v4.widget.DrawerLayout>
Use replace() to replace an existing fragment in a container with an instance of a new fragment class that you provide. Calling replace() is equivalent to calling remove() with a fragment in a container and adding a new fragment to that same container. transaction.
ViewPager save Fragment in FragmentManager with particular tags, So We can get ViewPager's fragment by FragmentManager class by providing tag. And ViewPager provide tag to each fragment by following syntax : Fragment page = getSupportFragmentManager(). findFragmentByTag("android:switcher:" + R.
You need add/commit fragment using one tag ex. "TAG_FRAGMENT". Fragment fragment = getSupportFragmentManager(). findFragmentByTag(TAG_FRAGMENT); if(fragment !=
The FragmentManager class and the FragmentTransaction class allow you to add, remove and replace fragments in the layout of your activity at runtime.
After struggling with this for a few days I realized that the problem actually was that I passed the wrong FragmentManager to the ViewPager. It is indeed the FragmentManager returned by Fragment.getChildFragmentManager() that should be used. This makes sense, since it is the Fragment itself that stores the state of the child Fragments in the ViewPager. I am not sure why I couldn't make that work before, but now the LifeCycle of the app works fine with the following setup in my main Fragment's onCreate() method:
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.mm_main_fragment, container, false); mMMActivity = (MMActivity) container.getContext(); mMMActivity.getSupportActionBar().setTitle(getString(R.string.mm_toolbar_title_main_fragment)); // Setup ViewPager. mViewPager = (ViewPager) view.findViewById(R.id.mm_pager); mAdapter = new MMPagerAdapter(getChildFragmentManager(), mMMActivity); // <-- This is the key mViewPager.setAdapter(mAdapter); mViewPager.setOffscreenPageLimit(PagerConstants.OFFSCREEN_PAGE_LIMIT); mViewPager.setCurrentItem(PagerConstants.PAGE_FILTER_RECIPES); // Setup TabLayout. TabLayout tabLayout = (TabLayout) view.findViewById(R.id.mm_tablayout); tabLayout.setupWithViewPager(mViewPager); return view; } This was also the solution of another question. As a side note, I use this solution to get the Fragments in my ViewPager and it works just fine with the LifeCycle.
In the research of this issue I created a test application for this. The ones interested can clone it from here. It has ugly colors.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With