I am launching some fragments from my options menu, for example a preference fragment and a dialog fragment.
When I open the preferences fragment and hit the back button the entire activity closes. This is not the case for the dialog fragment that works as I'd expect.
Could someone explain why this is happening and what is the accepted way of handling please? :)
To launch preference fragment:
getFragmentManager().beginTransaction()
.add(android.R.id.content, new SettingsFragment())
.addToBackStack("settings")
.commit();
Related to my answer here.
I had the exact same problem with the preferences fragment. It seems most people must give up and use a Preference Activity instead.
Your first problem that you are going to encounter is that you need to use replace instead of add when launching the fragment. That code should change to look like so:
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new SettingsFragment())
.addToBackStack("settings")
.commit();
As for handling the back button, it appears that the "back stack" is something that is not automatically triggered with the system back button. My solution was to manually pop the back stack from the onBackPressed override:
#Override
public void onBackPressed()
{
if (inSettings)
{
backFromSettingsFragment();
return;
}
super.onBackPressed();
}
Whenever I navigate to my preferences fragment, I set the inSettings boolean to true in the activity to retain that state. Here is what my backFromSettingsFragment method looks like:
private void backFromSettingsFragment()
{
inSettings = false;
getFragmentManager().popBackStack();
}
If there is not any clear reason you named your back stack state, use null as argument:
getFragmentManager().beginTransaction()
.add(android.R.id.content, new SettingsFragment())
.addToBackStack(null)
.commit();
Related
guys.
This must be a silly question but i'm not managing to work this out. My scenario is: i have in my MainActivity a BottomNavigation which i navigate over three fragments. And my problem is that when the back button (from android bottom navigation toolbar) is pressed the previous fragment opens but i want the app to close. So my question is: how i manage to prevent the previous fragments to open?
PS: I know it has something to do with FragmentMananger back stack but i did not understand how to use it.
PS2: Sorry for bad english.
The fragments are on the backstack.
Edit:
Work with FragmentTransaction and use addToBackStack (null)
// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
Override the onBackPressed() method in your activity.
#Override
public void onBackPressed() {
finish(); //This would close the app
}
Word of caution. This would close the activity in all the cases when the user presses back. To avoid this perhaps you would like to create something like this:
#Override
public void onBackPressed() {
if(someCondition) {
finish(); //This would close the activity
}
else {
super.onBackPressed(); //Fallbacks to default Android implementation
}
}
I have 3 fragments Fragment1, Fragment2 and Fragment3 and navigation is like
Fragment1->Fragment2->Fragment3
But on back press From Fragment3 go back to Fragment2 after completing some task like from Fragment2. And from Fragment1 finish this activity.
what will be the best method to do this task.
As per your question just you have to add addToBackStack() method before commit() transaction.
for example:
FirstFragment firstFragment = new FirstFragment();
getSupportFragmentManager().beginTransaction()
.replace(R.id.article_fragment, firstFragment)
.addToBackStack(null).commit();
Add second and third fragment same as above manner and just add code in onBackPressed() Override method.
for example:
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
Please follow the process.
1. When you add fragment add below code to your code
fragmentTransaction.addToBackStack(null);
Then back button handle from the activity.
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount()>0){
getFragmentManager().popBackStack();
}else {
super.onBackPressed();
} }
It will be working perfectly. Happy coding.
There is no need to handle OnBackPress inside Fragment. When you performing Fragment transaction, you can put your fragment to BackStack:
When there are FragmentTransaction objects on the back stack and the user presses the Back button, the FragmentManager pops the most recent transaction off the back stack and performs the reverse action (such as removing a fragment if the transaction added it).
More details you can get from this article.
I have one activity containing one container that will receive 2 fragments.
Once the activity initialises i start the first fragment with:
showFragment(new FragmentA());
private void showFragment(Fragment fragment) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, fragment, fragment.getTag())
.addToBackStack(fragment.getTag())
.commit();
}
Then when the user clicks on FragmentA, I receive this click on Activity level and I call:
showFragment(new FragmentB());
Now when I press back button it returns to fragment A (thats correct) and when i press again back button it show empty screen (but stays in the same activity). I would like it to just close the app (since the activity has no parent).
There are a lot of posts related with Fragments and backstack, but i can't find a simple solution for this. I would like to avoid the case where I have to check if im doing back press on Fragment A or Fragment B, since i might extend the number of Fragments and I will need to maintain that method.
You are getting blank screen because when you add first fragment you are calling addToBackStack() due to which you are adding a blank screen unknowingly!
now what you can do is call the following method in onBackPressed() and your problem will be solved
public void moveBack()
{
//FM=fragment manager
if (FM != null && FM.getBackStackEntryCount() > 1)
{
FM.popBackStack();
}else {
finish();
}
}
DO NOT CALL SUPER IN ONBACKPRESSED();
just call the above method!
addToBackStack(fragment.getTag())
use it for fragment B only, not for fragment A
I think your fragment A is not popped out correctly, try to use add fragment rather replace to have proper back navigation, however You can check count of back stack using:
FragmentManager fragmentManager = getSupportFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
and you can also directly call finish() in activity onBackPressed() when you want your activity to close on certain fragment count.
In my Activity, I add the first Fragment (myFragmentA) in onCreate() method:
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
.add(containerViewId, myFragmentA, fragmentTag)
.addToBackStack(null)
.commit();
In this first fragment, when I click on a Button, I add a new fragment (myFragmentB):
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
.replace(containerViewId, myFragmentB, fragmentTag)
.addToBackStack(null)
.commit();
All is great to this point.
When I catch back pressed from Activity:
#Override
public void onBackPressed() {
// >>> Just to prevent to keep the first fragment !!
if (getSupportFragmentManager().getFragments().size() > 1) {
getSupportFragmentManager().popBackStack();
}
}
Back pressed from myFragmentB > myFragmentA : OK
Back pressed from myFragmentA (just to verify there is no popback) : FAILED. myFragmentA is removed too! And I want to keep it always. I don't know why getSupportFragmentManager().getFragments().size() is equal 2!
Thanks for your helps guys!
If i correctly understood.
You want to use
getBackStackEntryCount()
instead of
getFragments().size()
Take a look at the docs: http://developer.android.com/reference/android/support/v4/app/FragmentManager.html
You see, your container activity gets added to the back stack by default. In your code you have written addToBackStack(null) for both the fragments, this will add your fragments to the back stack over and above your activity which gets added to the back stack by default. This explains why you are seeing size 2, one may be your activity and the other your fragment.
Is it possible to keep a Fragment running while I call replace from FragmentManager to open a new Fragment?
Basically I don't want to pause a Fragment when navigating (via replace method) to another Fragment.
Is it possible?
Or the correct approach is, always, instantiate a new Fragment every time I need to open it and restore its previous state?
Thanks!
FragmentManger replace method will destroy the previous fragment completely, So in each transaction onDestroyView(), onDestroy() and onDetach() will get called on previous fragment. If you want to keep your fragment running you can instead use FragmentManger hide() and show() methods! It hides and shows the fragments without destroying them.
so first add both fragments to fragment manager and also hide the second fragment.
fragmentManager.beginTransaction()
.add(R.id.new_card_container, FragmentA)
.add(R.id.new_card_container,FragmentB)
.hide(FragmentB)
.commit();
Note that you can only call show() on hidden fragment. So here you can't call show() on FragmentA but it's not a problem because by hiding and showing FragmentB you can get replacement effect you want.
And here is a method to go back and forth between your fragments.
public void showOtherFragment() {
if(FragmentB.isHidden()){
fragmentManager.beginTransaction()
.show(FragmentB).commit();
} else {
fragmentManager.beginTransaction()
.hide(FragmentB).commit();
}
}
Now if you put log message in fragment callback method you will see there is no destruction (except for screen orientation change!), even view will not get destroyed since onDistroyView doesn't get called.
There is only one problem and that is, first time when application starts onCreateView() method get called one time for each fragment (and it should be!) but when the orientation changes onCreateView() gets called twice for each fragment and that's because fragments once created as usual and once because of there attachment to FragmentManger (saved on bundle object) To avoid that you have two options 1) detach fragments in onSaveInstaneState() callback.
#Override
protected void onSaveInstanceState(Bundle outState) {
fragmentManager.beginTransaction()
.detach(FragmentA)
.detach(FragmentB)
.commit();
super.onSaveInstanceState(outState);
}
It's working but view state will not get updated automatically, for example if you have a EditText its text will erase each time orientation change happens. of course you can fix this simply by saving states in the fragment but you don't have to if you use the second option!
first i save a Boolean value in onSaveInstaneState() method to remember witch fragment is shown.
#Override
protected void onSaveInstanceState(Bundle outState) {
boolean isFragAVisible = true;
if(!FragmentB.isHidden())
isFragAVisible = false;
outState.putBoolean("isFragAVisible",isFragAVisible);
super.onSaveInstanceState(outState);
}
now in activity onCreate method i check to see if savedInstanceState == null. if yes do as usual if not activity is created for second time. so fragment manager already contains the fragments. So instead i'm getting a reference to my fragments from fragment manager. also i make sure correct fragment is shown since its not recovered automatically.
fragmentManager = getFragmentManager();
if(savedInstanceState == null){
FragmentA = new FragmentA();
FragmentB = new FragmentB();
fragmentManager.beginTransaction()
.add(R.id.new_card_container, FragmentA, "fragA")
.add(R.id.new_card_container, FragmentB, "fragB")
.hide(FragmentB)
.commit();
} else {
FragmentA = (FragmentA) fragmentManager.findFragmentByTag("fragA");
FragmentB = (FragmentB) fragmentManager.findFragmentByTag("fragB");
boolean isFragAVisible = savedInstanceState.getBoolean("isFragAVisible");
if(isFragAVisible)
fragmentManager.beginTransaction()
.hide(FragmentB)
.commit();
else
fragmentManager.beginTransaction()
.hide(FragmetA) //only if using transaction animation
.commit();
}
By now your fragment will work perfectly if are not using transaction animation. If you do, you also need to show and hide FragmentA. So when you want to show FragmentB first hide FragmentA then show FragmentB (in the same transaction) and when you want to hide FragmentB hide it first and also show FragmentA (again in the same transaction). Here is my code for card flip animation (downloaded from developer.goodle.com)
public void flipCard(String direction) {
int animationEnter, animationLeave;
if(direction == "left"){
animationEnter = R.animator.card_flip_right_in;
animationLeave = R.animator.card_flip_right_out;
} else {
animationEnter = R.animator.card_flip_left_in;
animationLeave = R.animator.card_flip_left_out;
}
if(cardBack.isHidden()){
fragmentManager.beginTransaction()
.setCustomAnimations(animationEnter, animationLeave)
.hide(cardFront)
.show(cardBack)
.commit();
} else {
fragmentManager.beginTransaction()
.setCustomAnimations(animationEnter,animationLeave)
.hide(cardBack)
.show(cardFront)
.commit();
}
}