1

I have an Android activity that holds and manages six fragments, is fragment is a step in a flow, some of the fragments are replaced and some of them are added.

The Activity just uses a Framelayout as the container for the fragments as follows:

<FrameLayout
        android:id="@+id/content"
        android:layout_below="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

Then the flow of the fragments is like this:

//Activity starts, add first Fragment

 fragmentManager.beginTransaction().replace(R.id.content, FirstFragment.newInstance(listOfItems)).commit();

then

//User pressed button, activity got callback from first fragment

FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.content, fragment2);
transaction.addToBackStack("frag2");
transaction.commit();

then

//Another callback from Frag2, perform the add of frag 3

FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.content, fragment3);
transaction.addToBackStack("frag3");
transaction.commit();

And so on....

I also manage the back stack from the Activity like this:

//Controlling the back stack when the user selects the soft back button in the toolbar
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                if (fragmentManager.getBackStackEntryCount() == 0) {
                    super.onBackPressed();
                    overridePendingTransition(R.anim.no_change, R.anim.slide_down);
                } else {
                    if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
                        super.onBackPressed();
                        Fragment fragment = fragmentManager.getFragments()
                                .get(fragmentManager.getBackStackEntryCount());
                        fragment.onResume(); //Make sure the fragment that is currently at the top of the stack calls its onResume method
                    }
                }
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    //Controlling the back stack when the user selects the "hardware" back button
    @Override
    public void onBackPressed() {
        if (fragmentManager.getBackStackEntryCount() == 0) {
            super.onBackPressed();
            overridePendingTransition(R.anim.no_change, R.anim.slide_down);
        } else {
            if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
                super.onBackPressed();
                Fragment fragment = fragmentManager.getFragments()
                        .get(fragmentManager.getBackStackEntryCount());
                fragment.onResume(); //Make sure the fragment that is currently at the top of the stack calls its onResume method
            }
        }
    }

My problem is that I open the app and go to this Activity which loads the fragments and then go through the flow to a certain stage ( I haven't narrowed it down yet) then I press the home button and blank my screen. Now after a certain amount of time when I open the app again it opens on the fragment I left but everything seems to be messed up, when I press back it seems to pop the wrong fragment and the UI becomes mixed up with the different fragments.

My guess is that when I open the app again the Activity onResume or the Fragment onResume or some lifecycle event is being called that I am not handling correctly?

So I was wondering is there best practices, guidelines or patterns that should be adhered to when using a Fragment pattern like I am doing so?

Donal Rafferty
  • 19,707
  • 39
  • 114
  • 191
  • 2
    I don't think this is the cause of the problem, but there's no need to call "onResume" on the Fragment that's being re-added from the backstack. If the fragment was removed from a "replace", then "onResume" will be called again when it is re-added. If the fragment was never removed, then the state would remain anyway. – DeeV Aug 10 '16 at 15:15
  • Yes I agree but you are correct that isn't the cause of the issue, all I do in the onResume of the fragments is call the Activity to set the title of the toolbar. – Donal Rafferty Aug 10 '16 at 15:22
  • Well, you have to call "super.onResume()" on the Fragment itself because it needs to do its own things, and calling it out of order may cause unexpected behavior. – DeeV Aug 10 '16 at 15:27
  • Why did you use `transaction.add` with fragment3? (Anyway I think `one Activity and many Fragments` pattern is very difficult to maintain.) – nshmura Aug 10 '16 at 15:50
  • Because I want to add fragment 3 on top of fragment 2 and keep fragment 2 there as fragment 3 is a user selection and then they move back to fragment 2 – Donal Rafferty Aug 10 '16 at 16:44

1 Answers1

1

Since you have so many fragments in one activity, and they use the same container, that means all fragments are in the same place, and only one fragment will show at a time.

So why don't you use ViewPager and let FragmentPagerAdapter manager these fragments? In this way, you do not need to manager fragment lifecycle by yourself, you just need to override FragmentPagerAdapter methods:

  • to create fragment instance by getItem,
  • to update fragment by getItemPosition and Adapter.notifyDataSetChanged(),
  • to show selected fragment by mViewPager.setCurrentItem(i)

Code snippets, detail refer to https://github.com/li2/Update_Replace_Fragment_In_ViewPager/

private FragmentPagerAdapter mViewPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
    @Override
    public int getCount() {
        return PAGE_COUNT;
    }

    // Return the Fragment associated with a specified position.
    @Override
    public Fragment getItem(int position) {
        Log.d(TAG, "getItem(" + position + ")");
        if (position == 0) {
            return Page0Fragment.newInstance(mDate);
        } else if (position == 1) {
            return Page1Fragment.newInstance(mContent);
        }

        return null;
    }

    @Override
    // To update fragment in ViewPager, we should override getItemPosition() method,
    // in this method, we call the fragment's public updating method.
    public int getItemPosition(Object object) {
        Log.d(TAG, "getItemPosition(" + object.getClass().getSimpleName() + ")");
        if (object instanceof Page0Fragment) {
            ((Page0Fragment) object).updateDate(mDate);
        } else if (object instanceof Page1Fragment) {
            ((Page1Fragment) object).updateContent(mContent);
        }
        return super.getItemPosition(object);
    };
};
Weiyi
  • 1,843
  • 2
  • 22
  • 34
  • Will a FragmentPagerAdapter be suitable for Fragments that are dynamic in nature and require communication between each other, on the Android docs here - https://developer.android.com/reference/android/support/v4/app/FragmentPagerAdapter.html - it states "This version of the pager is best for use when there are a handful of typically more static fragments to be paged through" – Donal Rafferty Aug 11 '16 at 07:40
  • What's your **dynamic** means? just data changed? – Weiyi Aug 11 '16 at 07:54
  • 1
    ViewPager has two adapter: `FragmentPagerAdapter` and `FragmentStatePagerAdapter`. The difference between is that the second one will destroy unneeded fragments, the first one will keep all fragments... So if you have a small, fixed number of fragments, FragmentPagerAdapter is appropriate. If you have a long list, such as a email list, you should use FragmentStatePagerAdapter to save memory. – Weiyi Aug 11 '16 at 08:05
  • By dynamic I mean they need to communicate between each other and state changes between the fragments dependent on actions taken on other fragments – Donal Rafferty Aug 11 '16 at 08:43
  • Android API Guides says `a handful of typically more static fragments`, I think the "static" in this sentence means something like this: the view layout is fixed but the data maybe changed. If everything is static, you can just draw a static view, there is no need to use fragment which will make things complex. So base on your description, I think `FragmentPagerAdapter` is appropriate for you. – Weiyi Aug 11 '16 at 09:03