9

Scenario what i'm trying to achieve:

  1. Loading activity with two frame containers (for list of items and for details).
  2. At the app launch time add listFragment in listFrame and some initial infoFragment in detailsFrame containers.
  3. Navigating through list items without adding each detail transaction to back stack (want to keep only infoFragment in stack).
  4. As soon as user hit back button (navigate back) he falls back to intial infoFragment what was added in launch time.
  5. If sequential back navigation fallows then apps exit.

My code:

        protected override void OnCreate(Bundle savedInstanceState)
        {
...
            var listFrag = new ListFragment();
            var infoFrag = new InfoFragment();
            var trans = FragmentManager.BeginTransaction();
            trans.Add(Resource.Id.listFrame, listFrag);
            trans.Add(Resource.Id.detailsFrame, infoFrag);
            trans.Commit();
...
        }

        public void OnItemSelected(int id)
        {
            var detailsFrag = DetailFragment.NewInstance(id);
            var trans = FragmentManager.BeginTransaction();
            trans.Replace(Resource.Id.detailsFrame, detailsFrag);
            if (FragmentManager.BackStackEntryCount == 0)
                {
                    trans.AddToBackStack(null);
                }
            trans.Commit();
        }

My problem:

After back button has been hit, infoFrag is overlapped with previous detailFrag! Why?

Arvis
  • 8,273
  • 5
  • 33
  • 46
  • This answer here:- https://stackoverflow.com/a/28115271/9969285 is the easiest and best workaround! It worked in my case with multiple fragments (can be started in any order after first fragment), and back pressed will always return to first created fragment. Very important point to remember is not to add to backstack while creating first fragment, but add to backstack when adding all other fragments. Due to less reputation points I am not able to directly comment or up vote that answer. – Jemshid Jul 01 '18 at 19:57
  • its simple one line code go to this link & check – MohammedAli Jul 30 '18 at 07:42

5 Answers5

12

You can do this:

if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
   getSupportFragmentManager().popBackStack(getSupportFragmentManager().getBackStackEntryAt(0).getId(), getSupportFragmentManager().POP_BACK_STACK_INCLUSIVE);
} else {
   super.onBackPressed();}

In your activity, so you to keep first fragment.

You shouldn't have, in your first fragment, the addToBackStack. But, in the rest, yes.

Juan Labrador
  • 1,204
  • 10
  • 14
  • 'You shouldn't have, in your first fragment, the addToBackStack' should be in uppercase with blinking arrow pointing to it! Thank you so much!! – Schwesi Oct 31 '16 at 07:24
  • So, I added this to the container activity's `onBackPressed`. Then, I used `addFragment` for **Frag A**, then `replaceFragment + addToBackStack` when going to **Frag B** to keep Frag A, then `replaceFragment` when going to **Frag C**. Pressing back while on **Frag C** goes back to **Frag A**. (*lies down on bed in relief & agony*) – Aba Oct 01 '18 at 03:06
7

Very nice explanation by Budius. I read his advice and implemented similar navigation, which I would like to share with others.

Instead of replacing fragments like this:

Transaction.remove(detail1).add(detail2)
Transaction.remove(detail2).add(detail3)
Transaction.remove(detail3).add(detail4)

I added a fragment container layout in the activity layout file. It can be either LinearLayout, RelativeLayot or FrameLayout etc.. So in the activity on create I had this:

transaction.replace(R.id.HomeInputFragment, mainHomeFragment).commit();

mainHomeFragment is the fragment I want to get back to when pressing the back button, like infoFrag. Then, before EVERY NEXT transaction I put:

fragmentManager.popBackStackImmediate();
transaction.replace(R.id.HomeInputFragment, frag2).addToBackStack(null).commit();

or

fragmentManager.popBackStackImmediate();
transaction.replace(R.id.HomeInputFragment, frag3).addToBackStack(null).commit();

That way you don't have to keep track of which fragment is currenty showing.

mdzeko
  • 932
  • 10
  • 19
6

The problem is that the transaction that you're backing from have two steps:

  • remove infoFrag
  • add detailsFrag (that is the first1 detail container that was added)

(we know that because the documentation This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here. )

So whenever the system is reverting that one transaction is reverting exactly those 2 steps, and it say nothing about the last detailFrag that was added to it, so it doesn't do anything with it.

There're two possible work arounds I can think on your case:

  1. Keep a reference on your activity to the last detailsFrag used and use the BackStackChange listener to whenever the value change from 1 to 0 (you'll have to keep track of previous values) you also remove that one remaining fragment

  2. on every click listener you'll have to popBackStackImmediatly() (to remove the previous transaction) and addToBackStack() on all transactions. On this workaround you can also use some setCustomAnimation magic to make sure it all looks nice on the screen (e.g. use a alpha animation from 0 to 0 duration 1 to avoid previous fragment appearing and disappearing again.

ps. I agree that the fragment manager/transaction should be a bit more clever to the way it handles back stack on .replace() actions, but that's the way it does it.

edit:

what is happening is like this (I'm adding numbers to the details to make it more clear). Remember that .replace() = .remove().add()

Transaction.remove(info).add(detail1).addToBackStack(null) // 1st time
Transaction.remove(detail1).add(detail2) // 2nd time
Transaction.remove(detail2).add(detail3) // 3rd time
Transaction.remove(detail3).add(detail4) // 4th time

so now we have detail4 on the layout:

< Press back button >
     System pops the back stack and find the following back entry to be reversed
         remove(info).add(detail1);
     so the system makes that transaction backward.
     tries to remove detail1 (is not there, so it ignores)
     re-add(info) // OVERLAP !!!

so the problem is that the system doesn't realise that there's a detail4 and that the transaction was .replace() that it was supposed to replace whatever is in there.

Jared Burrows
  • 54,294
  • 25
  • 151
  • 185
Budius
  • 39,391
  • 16
  • 102
  • 144
  • i'm not quite sure i understand your explanation about the problem. If I go [infoFrag] > [detailsFrag] then press back I jump back to [infoFrag] as expected. Problem appear when do [infoFrag] > [detailsFrag] > [detailsFrag] then back. ahother case: http://stackoverflow.com/questions/10065640/fragment-replace-not-replacing-all-fragments – Arvis Jan 11 '13 at 17:32
  • let me put a transaction flow thing on my answer. give me a few mins – Budius Jan 11 '13 at 17:32
  • "so the problem is that the system doesn't realise that there's a detail4", that explains all. now the situation is understandable, Thanks! Just curious, how did you came to this enlightenment ;) ? – Arvis Jan 11 '13 at 20:53
  • I've just finished developing for the company I work for, as part of our internal library, a little controller for some fragment transactions, like in this question and check my answer http://stackoverflow.com/questions/14053730/how-to-write-an-android-multi-pane-app-with-very-deep-navigation/14155204#14155204 so until I get to the right place there were LOTS of trial and error and reading the source code, on it I'm basically re-doing the back stack in a bunch of different ways as per need. Did you understand the possible workarounds I proposed? If u need I can further explain. – Budius Jan 12 '13 at 01:10
  • 1
    it seems that part of doc where says about removing all current frags is misleading - has been removed only one fragment! "This is essentially the same as calling remove(Fragment) for **all currently added fragments** that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here)." – Arvis Jan 13 '13 at 14:19
  • it is removing all on the moment you execute the transaction, but the back stack is on the same fragment that was previously used during the transaction, it doesn't check for all the fragments on that view ID on the moment that the stack is being popped. As I said earlier, it's a bit dumb the way the Manager is operating here, but it's what we have to work with, so I presented those two possible work around. – Budius Jan 13 '13 at 14:21
  • small example without backStack involved : `getSupportFragmentManager().beginTransaction().add(R.id.fragment, new FragA()).add(R.id.fragment, new FragB()).commit(); getSupportFragmentManager().beginTransaction().replace(R.id.fragment, new FragC()).commit();` only FragA is getting removed – Arvis Jan 13 '13 at 15:08
  • you have to put a `fragManager.executePendingTransactions()` between those two. The `commit()` only schedules the transactions for a later pass. – Budius Jan 13 '13 at 15:28
0

You could just override onBackPressed and commit a transaction to the initial fragment.

Tim Malseed
  • 6,003
  • 6
  • 48
  • 66
  • Actually i don't want to interact with Back Pressed callback as want to leave that button to work as supposed, beside navigating beck from info fragment app must exit (that is first beck stack transaction). – Arvis Jan 11 '13 at 00:05
0

I'm guessing but:

You've added the transaction to replace infoFrag with 1st detailsFrag into the backstack.

But then you replace 1st detailsFrag with 2nd detailsFrag.

At this point when you click back, the fragment manager cannot cleanly replace 1st detailsFrag with infoFrag as 1st detailsFrag has already been removed and replaced.

Whether the overlapping behaviour is expected or not I don't know.

I would suggest debugging the Android core code to see what it is doing.

I'm not sure whether you can achieve without say overriding Activity::onBackPressed() and doing the pops yourself having added all transactions to the backstack.

PJL
  • 18,735
  • 17
  • 71
  • 68
  • From me overlapping behaviour is not expected at all :) But what about BackStack management, that is whole purpose of it to manualy( explicity) add or pop transaction in/from back stack, or isn't it?! And if i put first transaction in backstack i expecting it to be there after back navigation, but there ir more than one. – Arvis Jan 11 '13 at 12:01
  • Maybe something [here](http://stackoverflow.com/questions/12529499/problems-with-android-fragment-back-stack) of use – PJL Jan 11 '13 at 12:39