Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fragments not always being replaced when using back button

I'm using actionbar tabs because I need the navigation elements to be on every page. I'm using ActionBarSherlock for backwards compatibility (minimum API 8, target API 17). My MainActivity extends SherlockFragmentActivity. In my onCreate() for that, I have

ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

actionBar.setDisplayShowTitleEnabled(true);

Tab tab1 = actionBar.newTab().setText("My Pages")
    .setTabListener(new MyPagesFragment());

Tab tab2 = actionBar.newTab().setText("Search")
    .setTabListener(new SearchFragment());

Tab tab3 = actionBar.newTab().setText("About")
    .setTabListener(new AboutFragment());

// Start with the second tab selected.
actionBar.addTab(tab1, 0, false);
actionBar.addTab(tab2, 1, true);
actionBar.addTab(tab3, 2, false);

All the tab fragments are SherlockListFragments that implement ActionBar.TabListener, and do this

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
    ft.replace(android.R.id.content, this, "mypages");
}

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
    ft.remove(this);
}

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
    // Force a complete reload.
    onTabSelected(tab, ft);
}

The search page has an EditText and uses its value in an AsyncTask to fetch data from an API and add it to an SQLite database, before calling

((MainActivity) getActivity()).showDetailView(responseCode);

to show the details, which is a method in my MainActivity as follows:

protected void showDetailView(long codeID) {
    SherlockFragment detailFragment = new DetailFragment();
    Bundle args = new Bundle();
    args.putLong("codeID", codeID);
    detailFragment.setArguments(args);

    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(android.R.id.content, detailFragment);
    ft.addToBackStack(null);
    ft.commit();
}

DetailFragment is a SherlockFragment that uses getArguments() to retrieve the codeID--

Bundle args = getArguments();
if (null != args) {
  codeRowID = args.getLong("codeID");
}

--and reads the matching data from the database to display it. Said data often contains links to more details, clicking which causes showDetailView to be called again with the new codeID.

MyPages is a list of all the cached details pages, and it too calls showDetailView:

@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
  ((MainActivity) getActivity()).showDetailView(pages[position].codeId);
}

Going forward, this appears to work fine. However, when I use the back button, sometimes the fragments stick around, so they're still visible behind the restored fragment. How do I stop this happening?


I think the problem is maybe that the tabs are not being added to the backstack? But when I try to do that, they throw an exception telling me they can't be added to the backstack, so I don't understand how you are supposed to handle this. I don't understand why this thing, which it seems to be should be an incredibly basic navigation thing that lots of people want to do -- the back key has been on every android phone and table I've ever seen, physically or software! --apparently has no known solution. Have I just fundamentally misunderstood their use? Are fragments just not supposed to be used in this situation? How else do you do persistent navigation elements without repeating the same code on every page?


Logging - start app -

07-20 23:33:49.521: D/NAVIGATION_TRACE(7425): MAIN - onCreate
07-20 23:33:50.013: D/NAVIGATION_TRACE(7425): SEARCH - onTabSelected
07-20 23:33:50.021: D/NAVIGATION_TRACE(7425): SEARCH - onCreateView
07-20 23:33:50.060: D/NAVIGATION_TRACE(7425): SEARCH - onActivityCreated
07-20 23:33:50.060: D/NAVIGATION_TRACE(7425): MAIN - onResume

Search now visible. Search for an item to bring up detail view -

07-20 23:34:52.123: D/NAVIGATION_TRACE(7425): SEARCH - handleResponseCode
07-20 23:34:52.123: D/NAVIGATION_TRACE(7425): MAIN - showDetailView - 31

Detail now visible; Search gone. Click mypages tab -

07-20 23:35:37.787: D/NAVIGATION_TRACE(7425): SEARCH - onTabUnselected
07-20 23:35:37.787: D/NAVIGATION_TRACE(7425): MYPAGES - onTabSelected
07-20 23:35:37.826: D/NAVIGATION_TRACE(7425): MYPAGES - onCreateView
07-20 23:35:37.873: D/NAVIGATION_TRACE(7425): MYPAGES - onActivityCreated

MyPages now visible; Detail gone. Click back button -

07-20 23:36:12.130: D/NAVIGATION_TRACE(7425): SEARCH - onCreateView
07-20 23:36:12.201: D/NAVIGATION_TRACE(7425): SEARCH - onActivityCreated

Search and MyPages now both showing.


MainActivity:

public class MainActivity extends SherlockFragmentActivity 
{

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {   
        super.onCreate(savedInstanceState);
        Log.d("NAVIGATION_TRACE", "MAIN - onCreate");

        ActionBar actionBar = getSupportActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        actionBar.setDisplayShowTitleEnabled(true);

        Tab tab1 = actionBar.newTab().setText("My Pages")
            .setTabListener(new TabListener<MyPagesFragment>(
            this, "mypages", MyPagesFragment.class));

        Tab tab2 = actionBar.newTab().setText("Search")
            .setTabListener(new TabListener<SearchFragment>(
            this, "search", SearchFragment.class));

        Tab tab3 = actionBar.newTab().setText("About")
            .setTabListener(new TabListener<AboutFragment>(
            this, "about", AboutFragment.class));

        // Start with the second tab selected.
        actionBar.addTab(tab1, 0, false);
        actionBar.addTab(tab2, 1, true);
        actionBar.addTab(tab3, 2, false);
    }

    @Override
    public void onBackPressed()
    {
        FragmentManager fm = getSupportFragmentManager();
        if (0 < fm.getBackStackEntryCount())
        {
            super.onBackPressed();
        } else {
            // prompt to quit
            AlertDialog.Builder alertErrorResponse = new AlertDialog.Builder(this);
            alertErrorResponse.setMessage("Close app?");
            alertErrorResponse.setNegativeButton("Cancel", null);
            alertErrorResponse.setPositiveButton("OK", new DialogInterface.OnClickListener()
            {
            @Override
            public void onClick(DialogInterface dialog, int which)
            {
                finish();
            }
            });
            alertErrorResponse.show();
        }
    }

    public void showDetailView(long codeID) {
        Log.d("NAVIGATION_TRACE", "MAIN - showDetailView - "+String.valueOf(codeID));
        lastShownCode = codeID;
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();
        SherlockFragment detailFragment = new DetailFragment();
        Bundle args = new Bundle();
        args.putLong("codeID", codeID);
        detailFragment.setArguments(args);
        ft.replace(android.R.id.content, detailFragment, "details");
        ft.addToBackStack(null);
        ft.commit();
    }

    public class TabListener<T extends SherlockListFragment> implements ActionBar.TabListener
    {
        private final SherlockFragmentActivity mActivity;
        private final String mTag;
        private final Class<T> mClass;
        private SherlockListFragment mFragment;

        public TabListener (SherlockFragmentActivity activity, String tag, Class<T> clz) 
        {
            Log.d("NAVIGATION_TRACE", "TabListener - "+tag+" - "+clz.getCanonicalName());
            mActivity = activity;
            mTag = tag;
            mClass = clz;

            FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
            mFragment = (SherlockListFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);
            if (mFragment != null && !mFragment.isDetached()) 
            {
                Log.d("NAVIGATION_TRACE", "DETACH - "+mTag);
                removeDetail(ft);
                ft.detach(mFragment);
            }
            ft.commit();
        }

        public void clearBackStack()
        {
            Log.d("NAVIGATION_TRACE", "clearBackStack - "+mTag);
            FragmentManager fm = mActivity.getSupportFragmentManager();
            if (null != fm && 0 < fm.getBackStackEntryCount())
            {
            fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
            }
        }

        @Override
        public void onTabSelected(Tab tab, FragmentTransaction ft) 
        {
            Log.d("NAVIGATION_TRACE", "onTabSelected - "+mTag);

            clearBackStack();

            ft = mActivity.getSupportFragmentManager().beginTransaction();

            if (mFragment == null) 
            {
                Log.d("NAVIGATION_TRACE", "ADD/SHOW - "+mClass.getName());
                removeDetail(ft);
                mFragment = (SherlockListFragment) SherlockListFragment.instantiate(mActivity, mClass.getName());
                ft.add(android.R.id.content, mFragment, mTag);
                ft.commit();
            } 
            else 
            {
                Log.d("NAVIGATION_TRACE", "ATTACH/SHOW - "+mClass.getName());
                removeDetail(ft);
                ft.attach(mFragment);
                ft.show(mFragment);
                ft.commit();
            }

        }

        @Override
        public void onTabUnselected(Tab tab, FragmentTransaction ft) 
        {        
            Log.d("NAVIGATION_TRACE", "onTabUnselected - "+mTag);
            ft = mActivity.getSupportFragmentManager().beginTransaction();

            if (null != mFragment) 
            {
                Log.d("NAVIGATION_TRACE", "HIDE/DETACH - "+mTag);
                removeDetail(ft);
                ft.hide(mFragment);
                ft.detach(mFragment);
                ft.commitAllowingStateLoss();
            }
        }

        @Override
        public void onTabReselected(Tab tab, FragmentTransaction ft) {
        }

        public void removeDetail(FragmentTransaction ft) {
            SherlockFragment detailFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag("details");
            if (null != detailFragment && !detailFragment.isDetached()) {
            Log.d("NAVIGATION_TRACE", "DETACH - details");
            ft.detach(detailFragment);
            }
        }
    }   
}

Example fragment - MyPagesFragment :

public class MyPagesFragment extends SherlockListFragment implements
        OnItemClickListener
    {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState)
    {
        Log.d("NAVIGATION_TRACE", "MYPAGES - onCreateView");
        View view = inflater.inflate(R.layout.mypages, null);

        // code to set up list adapter here
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.d("NAVIGATION_TRACE", "MYPAGES - onActivityCreated");
        getListView().setOnItemClickListener(this);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View v, int position, long id)
    {
        Log.d("NAVIGATION_TRACE", "MYPAGES - onItemClick");
        ((MainActivity) getActivity()).showDetailView(pages[position].codeId);
    }
}

DetailFragment

public class DetailFragment extends SherlockFragment implements
        OnItemClickListener
{

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState)
    {       
        View view = inflater.inflate(R.layout.detail, null);

        // bunch of display / list set up code goes here

        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);    
        lvLinks.setOnItemClickListener(this);   
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id)
    {
        // Details page can open other details pages:
        ((MainActivity) getActivity()).showDetailView(pages[position].id);
    }

}

Note: The changes added were made following an answer by Sheldon (who seems to have, annoyingly, deleted their answer and comments), which is why the TabListener code changed between the original section and the later posted code.

I've currently hacked a solution by emptying the backstack on every tab select, and treating back on a top tab as an exit-the-app request, which is okay, I guess, but I really would like users to be able to keep backing through the tabs if this is at all possible (because that way, eg, if I was five details pages deep and I stopped to quickly search for something which, it turned out, didn't exist, I can back to those detail pages and still go up one or more to follow different detail links.)

like image 685
vestofwarlock Avatar asked Jul 13 '13 14:07

vestofwarlock


1 Answers

Try this..

    FragmentManager manager = getSupportFragmentManager();
    if (manager.getBackStackEntryCount() > 0)
        getSupportFragmentManager().popBackStack();
    else
        finish();
like image 50
selva_pollachi Avatar answered Nov 03 '22 17:11

selva_pollachi