Search code examples
androidandroid-fragmentsfragmenttransactionfragmentmanager

Android Fragments - Multiple tabs/pageflows, custom backstack


I'm running into memory issues with fragments and I could use some help as to the appropriate path to take. I cannot use a simple backstack because the application needs to retain several paths that the user takes within the application (and the user can jump back and forth). The navigation handles fragments in this way:

transaction.hide(currentFragment).show(newFragment).commit();

What I think would help my situation is having the view of the fragment temporarily destroyed and then recreated when the fragment gets placed back in view (instead of simply hiding the UI from the user's view). From reading the API, it doesn't sound like the hide method does this. Does anyone know if there are some built-in methods to the FragmentTransaction/FragmentManager/Fragment class that will allow me to do this?

Another option I'm considering is creating my own lifecycle for each fragment. I've explored using tabhost as well, but it doesn't appear it's going to solve the memory issues. If you have another idea, I'm open to it.

Thanks guys, I appreciate your help with this.


Solution

  • I came up with a solution to achieve a better memory managed solution (adapter-based) for having multiple user journeys saved at the same time (independent tabs with something that represents backstacks). I scrapped using the FragmentManager as it was not conducive to this behavior. I will leave my answer here in case anyone else runs into a similar problem of needing this idea of multiple backstacks using ViewPagers with FragmentStatePagerAdapter - one viewpager for each tab in their app (this can still be cleaned up more but you can get the jist):

    Primary Activity:

    public class MultiTabbedActivity extends FragmentActivity implements NavigationFragment.OnNavigationSelectedListener
    {
    
        private Page1Fragment mPage1;
        private Page2Fragment mPage2;
    
    
        private CustomFragment mCurrent;
        private String mNav;
    
        private List<CustomFragment> mPage1BackStack;
        private List<CustomFragment> mPage2BackStack;
    
        private CustomViewPager mPagerPage1;
        private CustomViewPager mPagerPage2;
    
        private boolean mUpdating = false;
    
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
    
            // initialize the backstacks
            mPage1BackStack = new ArrayList<CustomFragment>();
            mPage2BackStack = new ArrayList<CustomFragment>();
    
            mPage1 = new Page1Fragment();
            mPage2 = new Page2Fragment();
    
            mPage1BackStack.add(mPage1);
            mPage2BackStack.add(mPage2);
    
            mPagerPage1 = (CustomViewPager) findViewById(R.id.page1_fragment);
            mPagerPage2 = (CustomViewPager) findViewById(R.id.page2_fragment);
    
            mPagerPage1.setOffscreenPageLimit(3); //Customizable to determine how many 
            mPagerPage2.setOffscreenPageLimit(3); //fragments you want to hold in memory
    
            mPagerPage1.setAdapter(new CustomFragmentAdapter(getSupportFragmentManager(), mPage1BackStack));
            mPagerPage2.setAdapter(new CustomFragmentAdapter(getSupportFragmentManager(), mPage2BackStack));
    
            displayPager(mPagerPage1);
            mCurrent = mPage1;
            mNav = GlobalConstants.PAGE_PAGE1;
    
        }
    
        private void displayPager(ViewPager pager)
        {
            mPagerPage1.setVisibility(ViewPager.GONE);
            mPagerPage2.setVisibility(ViewPager.GONE);
    
            pager.setCurrentItem(pager.getAdapter().getCount() - 1);
            pager.getAdapter().notifyDataSetChanged();
            pager.setVisibility(ViewPager.VISIBLE);
        }
    
        @Override
        public void onNavSelected(String page)
        {
            if (page != null && page != "" && !mUpdating)
            {
                // Determine the Fragment selected
                if (page.equalsIgnoreCase(GlobalConstants.PAGE_PAGE1))
                {
                    mNav = GlobalConstants.PAGE_PAGE1;
                    displayPager(mPagerPage1);
                    mCurrent = mPage1BackStack.get(mPage1BackStack.size() - 1);
                }
                else if (page.equalsIgnoreCase(GlobalConstants.PAGE_PAGE2))
                {
                    mNav = GlobalConstants.PAGE_PAGE2;
                    displayPager(mPagerPage2);
                    mCurrent = mPage2BackStack.get(mPage2BackStack.size() - 1);
                }
            }
        }
    
    
        @Override
        public void onBackPressed()
        {
            PageFragment navFrag = null;
            // Update the Navigation Menu to indicate the currently visible
            // Fragment
            try
            {
                mUpdating = true;
    
                if (mPage1BackStack.size() > 1 && mNav.equalsIgnoreCase(GlobalConstants.PAGE_PAGE1))
                {
                    mPagerPage1.setCurrentItem(mPage1BackStack.size() - 2);
                    mPage1BackStack.remove(mPage1BackStack.size() - 1);
                    navFrag = mPage1BackStack.get(mPage1BackStack.size() - 1);
                    mPagerPage1.setAdapter(new CustomFragmentAdapter(getSupportFragmentManager(), mPage1BackStack));
                }
                else if (mPage2BackStack.size() > 1 && mNav.equalsIgnoreCase(GlobalConstants.PAGE_PAGE2))
                {
                    mPagerPage2.setCurrentItem(mPage2BackStack.size() - 2);
                    mPage2BackStack.remove(mPage2BackStack.size() - 1);
                    navFrag = mPage2BackStack.get(mPage2BackStack.size() - 1);
                    mPagerPage2.setAdapter(new CustomFragmentAdapter(getSupportFragmentManager(), mPage2BackStack));
                }
                else
                {
                    super.onBackPressed();
                }
    
                if (navFrag != null)
                    mCurrent = navFrag;
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
            finally
            {
                mUpdating = false;
            }
        }
    
        public void updateFragment(PageFragment newFragment)
        {
            if (mNav.equalsIgnoreCase(GlobalConstants.PAGE_PAGE1))
            {
                mPage1BackStack.add(newFragment);
                displayPager(mPagerPage1);
            }
            else if (mNav.equalsIgnoreCase(GlobalConstants.PAGE_PAGE2))
            {
                mPage2BackStack.add(newFragment);
                displayPager(mPagerPage2);
            }
            mCurrent = newFragment;
        }
    }
    

    CustomViewPager:

    public class CustomViewPager extends ViewPager {
    
        private boolean enabled;
    
        public CustomViewPager(Context context, AttributeSet attrs) {
            super(context, attrs);
            this.enabled = false;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (this.enabled) {
                return super.onTouchEvent(event);
            }
    
            return false;
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent event) {
            if (this.enabled) {
                return super.onInterceptTouchEvent(event);
            }
    
            return false;
        }
    
        public void setPagingEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    }
    

    CustomAdapter:

    public class CustomFragmentAdapter extends FragmentStatePagerAdapter
    {
        List<CustomFragment> mFragments;
    
        public CustomFragmentAdapter(FragmentManager fm, List<CustomFragment> fragments)
        {
            super(fm);
            mFragments = fragments;
        }
    
        public List<PageFragment> getmFragments()
        {
            return mFragments;
        }
    
        public void setmFragments(List<CustomFragment> mFragments)
        {
            this.mFragments = mFragments;
        }
    
        @Override
        public int getCount()
        {
            return mFragments.size();
        }
    
        @Override
        public Fragment getItem(int position)
        {
            return (Fragment) (mFragments.get(position));
        }
    }
    

    UI (wrapped inside a linear layout):

        <fragment
            android:id="@+id/navigation_fragment"
            android:name="your.package.here.NavigationFragment"
            android:layout_width="70dp"
            android:layout_height="match_parent" />
    
        <FrameLayout
            android:id="@+id/page_fragment"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="2" >
    
            <your.package.here.CustomViewPager
                android:id="@+id/page1_fragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:visibility="gone" />
            <your.package.here.CustomViewPager
                android:id="@+id/page2_fragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:visibility="gone" />
        </FrameLayout>