I have one activity, ActivityA, and 3 fragments, FragmentA, FragmentB, and FragmentC.
To add FragmentA, I use replace, where fragment is a new instance of FragmentA:
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.frame_layout_fragment, fragment)
.commit();
In the onCreate method of ActivityA, I do a check for FragmentA creation:
if (savedInstanceState == null) {
FragmentA fragmentA = FragmentA.newInstance();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.frame_layout_fragment, fragmentA)
.commit();
}
To add FragmentB and FragmentC, I use add and addToBackStack, where fragment is either a new instance of FragmentB or FragmentC:
getSupportFragmentManager()
.beginTransaction()
.add(R.id.frame_layout_fragment, fragment)
.addToBackStack(null)
.commit();
I press the back button while on FragmentC, it shows FragmentB, and pressing the back button while on FragmentB shows FragmentA, as expected. While I was on FragmentC, I rotate the device, and FragmentC still shows, as expected.
However, when I navigate from FragmentC to FragmentA using the 2 presses of the back button, then rotate the device twice, portrait -> landscape -> portrait, add FragmentB, add FragmentC, then I rotate the device once, I expected FragmentC to show, but instead FragmentB shows. When I press the back button once, nothing happens. When I press it again, it navigates back to FragmentA. It seems like FragmentC is present in the back stack, but for some reason its not visible.
Why is FragmentC not visible in this scenario?
I think this answer to another question is your answer too: Difference between add(), replace(), and addToBackStack()
It says:
One more importance difference between add and replace is: replace
removes the existing fragment and adds a new fragment. This means when
you press back button the fragment that got replaced will be created
with its onCreateView being invoked. Whereas add retains the existing
fragments and adds a new fragment that means existing fragment will be
active and they wont be in 'paused' state hence when a back button is
pressed onCreateView is not called for the existing fragment(the
fragment which was there before new fragment was added). In terms of
fragment's life cycle events onPause, onResume, onCreateView and other
life cycle events will be invoked in case of replace but they wont be
invoked in case of add.
Related
I'm having problem with fragment. Lets try to understand my issue, I have two fragment A and B. When app start with main activity,i start fragment A as you can see :
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,new MusicFragment())
.commit();
When i click on a button, it starts fragment B
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,new BarFragment())
.addToBackStack(null)
.commit();
Main problem is after starting fragment B,when i pressed back to go back to fragment A , Fragment A Recreated with new state.
I don' want to recreate fragment A. I only want to start fragment from old state where i left. How to fix it ?
Instead of calling the replace method you should be calling the add method with a subsequent call to addToBackStack with an argument of null. The add method adds the fragement to the stack with a null tag and calling the addToBackStack with an argument of null then the current fragment is stopped upon commit. If the method is not called then the current fragment is destroyed and recreated when coming back.
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container,new BarFragment())
.addToBackStack(null)
.commit();
You can clearly find it in the documentation quote saying this:
If you don't call addToBackStack() when you perform a transaction that
removes a fragment, then that fragment is destroyed when the
transaction is committed and the user cannot navigate back to it.
Whereas, if you do call addToBackStack() when removing a fragment,
then the fragment is stopped and is later resumed if the user
navigates back.
Here are some points that need to be taken care while creating fragments.
Check the backstack if the fragment is already created.
If it was created previously pop it from the backstack and put it on top so that it is visible to the user.
If fragment is not present in backstack crate it and store it in backstack.
Create a method like below which will handle such situation.
private void openFragment(Fragment fragment_to_be_opened){
String fragment_to_be_opened_name = fragment_to_be_opened.getClass().getName();
FragmentManager manager = getSupportFragmentManager();
// fetching the fragment if it is present in backstack
boolean fragment_allready_present = manager.popBackStackImmediate (fragment_to_be_opened_name, 0);
if (!fragment_allready_present){ //fragment is not present in backstack so create it and save the name in //backstack
FragmentTransaction fragment_trasition = manager.beginTransaction();
fragment_trasition.replace(R.id.fragment_container, fragment_to_be_opened);
fragment_trasition.addToBackStack(fragment_to_be_opened_name);
fragment_trasition.commit();
}
}
Now call this method from the activity to open a new fragment like
// create instance of fragment and pass it to the open fragment method
Fragment myFragment = new myFragment();
openFragment(myFragment);
MainFragment launches FragA that launches FragB that lunches FragC.
Back button press on FragB should go to FragA and back press on FragC should also go to FragA.
FragB is the only one where isToAddToBackStack is false.
childFragmentManager.commit {
replace(containerViewId, fragment, fragment::class.java.name)
if (isToAddToBackStack) {
addToBackStack(backStateName)
}
}
Whenever the back button is pressed on FragC, FragB is shown:
if (childFragmentManager.backStackEntryCount > 1) {
childFragmentManager.popBackStack()
return
}
What's the better way to achieve the navigation that I'm expecting?
Fragment transactions can involve two different types of tags. The one that most Android developers are familiar with is the Fragment tag, which you can use to find a specific Fragment in your FragmentManager later via findFragmentByTag(). This is useful for finding a Fragment when your application is in a particular state, but keep in mind that the Fragment needs to be added to your FragmentManager. If you have removed() or replaced() a Fragment and haven’t added it to the back stack, you won’t be able to find it.
The other type of tag is the BackStackRecord’s name passed into addToBackStack(). This name identifies a particular back stack record, which is a record of what occurred in a particular transaction. popBackStackImmediate() and its counterparts have variants that accept a back stack record name to pop the back stack to a particular state.
//fm is FragmentManager
// Fragment a is on the screen
Fragment a = new A_Fragment()
fm.beginTransaction()
.remove(null /*no fragments in R.id.content*/)
.add(R.id.content, aFragment, "fragment-a")
.commit();
// user wants to go from A to B
Fragment bFragment = new B_Fragment();
fm.beginTransaction()
.remove(fm.findFragmentById(R.id.content))
.add(R.id.content, bFragment, "fragment-b")
.addToBackStack("a")
.commit();
// user wants to go from B to C
fm.beginTransaction()
.remove(fm.findFragmentById(R.id.content))
.add(R.id.content, new C_Fragment(), "fragment-c")
.commit();
I use a FragmentTransaction to show my fragment inside my activity and also I add it to backstack like this:
getSupportFragmentManager().beginTransaction()
.addToBackStack(null)
.setCustomAnimations(R.anim.enter_from_left, R.anim.exit_to_right, R.anim.enter_from_right, R.anim.exit_to_left)
.replace(R.id.parentRelativeLayout, myFragment)
.commit();
When back button is pressed I check if myFragment is on top and if it is the case, I just call super.onBackPressed() to pop it from back stack and show previous fragment.
I want to know which event/callback on my fragment could help me to detect it is not shown on activity anymore.
I used onPause(), onDetach() and ... but they are not triggered (as it is dsecribed https://stackoverflow.com/a/16252923/1080355)
My App has only one activity and lots of fragments.
In my activty's XML, I just have a FrameLayout on which I replace/add/hide/show various fragments.
Imagine Fragment A is the first fragment the user sees when they open the app.
Click something in Fragment A to launch Fragment B and click something in Fragment B to launch Fragment C.
So the navigation is can be illustrated as follows :
Fragment A --> Fragment B --> Fragment C
I want to launch the app and show Fragment C directly from notification.
However, how can I provide back navigation from Fragment C, as such clicking back would go to Fragment B and clicking back again go to Fragment A ?
i.e How can I inject the following stack structure ?
Fragment A <-- Fragment B <-- Fragment C
Yes, you can do this. Since support library v26 you can build the stack with fragments without significant cost.
In your activity make the following:
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, new FragmentA())
.addToBackStack("fragmentA")
.setReorderingAllowed(true)
.commit();
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, new FragmentB())
.addToBackStack("fragmentB")
.setReorderingAllowed(true)
.commit();
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, new FragmentC())
.addToBackStack("fragmentC")
.setReorderingAllowed(true)
.commit();
Keep in mind that FragmentA and FragmentB will behave in a bit different way after pressing back on FragmentC due to setReorderingAllowed. onCreateView won't be called for FragmentA and FragmentB after they were added to stack, only in FragmentC onCreateView will be called. For FragmentA and FragmentB only onCreate will be called.
What you can do is -
Use a notification intent in which you pass a string. In your main activity if you receive that string make a fragment stack of A, B and C.
Else if you don't get the intent just continue your flow as it is.
When I perform getSupportFragmentManager().popBackStack(); it pops the back stack and performs the fragment transaction associated with the popped back stack entry.
I have 3 fragment:
- FragmentA
- FragmentB
- FragmentC
My activity performs 3 transactions when promoted by the user:
Fragment A is first added to my container.
Then FragmentA is detached and FragmentB is added. And the transaction is added to the back stack.
Then what I want is for FragmentC to replace only fragmentB and change the back stack entry such that when the user presses the back button, FragmentA should show up.
Here is my code that performs transaction 3:
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
fm.popBackStack();
ft.remove(fragmentB)
.add(R.id.container, fragmentC, tag)
.addToBackStack(null)
.commit();
Transactions 1 and 2 seem to work fine. However, when I attempt to perform transaction 3, what happens is that FragmentB is replaced by FragmentC but Fragment A also shows up right before FragmentC is drawn over it and FragmentA continues to be seen behind FragmentC.
What am I doing wrong? How can I replace FragmentB with FragmentC without having FragmentA reveal in the background.
I found a workaround if not a solution.
It seems that in transaction 3, when I remove FragmentB the previously detached FragmentA automatically gets added. That was what was causing FragmentA to be shown behind FragmentC.
The workaround is to detach FragmentA again after removing FragmentA like so:
ft.remove(fragmentB)
.detach(fragmentA) // Detach FragmentA that is automatically added by the removing of fragmentB
.add(R.id.container, fragmentC, tag)
.addToBackStack(null)
.commit();