I am trying to manage fragments in a very simple way. I have created a utility class that's adding fragments to the backstack and when we press the back button, the previous fragment shows up. I am OK with that. But, when I am trying to clear out all the fragments, and show up the root fragment, I am not able to do so properly. Following is my utility class:
public class FragmentUtil {
private FragmentUtil() {
}
public static void animatedReplace(FragmentActivity activity, int containerId, Fragment fragment, Bundle args, boolean addToBackStack){
FragmentTransaction transaction=activity.getSupportFragmentManager().beginTransaction();
fragment.setArguments(args);
if(addToBackStack){
transaction.addToBackStack(fragment.getClass().getName());
}
transaction.setCustomAnimations(R.anim.slide_from_right, R.anim.slide_to_left,R.anim.slide_from_left,R.anim.slide_to_right);
transaction.replace(containerId,fragment);
transaction.commit();
}
public static void clearBackStackToHome(FragmentActivity activity){
FragmentManager manager = activity.getSupportFragmentManager();
manager.popBackStack(DashboardFragment.class.getName(),FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
When I want to another fragment I just pass "true" for addToStack in animatedReplace method. When I want to clear the backstack I pass "false" and call the clearBackStackToHome method. Can anybody help me what I am doing wrong?
make a condition where check backStack is empty or not. when all fragments are removed from backStack then show your root fragment
Try this:
in your Utility class:
public void clearBackStack() {
FragmentManager manager = activity.getSupportFragmentManager();
if (manager.getBackStackEntryCount() > 0) {
FragmentManager.BackStackEntry first = manager
.getBackStackEntryAt(0);
manager.popBackStack(first.getId(),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
make sure in your root/first fragment animatedReplace() you pass boolean addToBackStack = false .
so that it will not be cleared.
EDIT
To use it in OnBackPressed() overwrite onBackPressed() in your activity and add the following code:
#Override
public void onBackPressed() {
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
FragmentManager.BackStackEntry first = fm
.getBackStackEntryAt(0);
fm.popBackStack(first.getId(),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
Log.i("MainActivity", "popping backstack");
} else {
super.onBackPressed();
}
}
Yes, you can manage fragment back press in onBackPressed() method of your MainActivity.
just override onBackPressed() , and check backStack count, if the count is zero then navigate to your root fragment or exit from app. Also, you can implement your code according to check which fragment in back stack.
#Override
public void onBackPressed() {
FragmentManager fm = getSupportFragmentManager();
int count = fm.getBackStackEntryCount();
if (count >= 1) {
if (fm.findFragmentById(R.id.main_container) instanceof yourFragment) {
super.onBackPressed();
}
} else {
// Show your root fragment here
}
}
Related
I'm adding the fragment using android.R.id.content in order to fill up Activity's space using the following code.
private void doFragmentTransaction(Fragment fragment, String tag) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in_bottom_y, R.anim.slide_out_bottom_y,
R.anim.slide_in_bottom_y, R.anim.slide_out_bottom_y);
transaction.add(android.R.id.content, fragment, tag);
transaction.addToBackStack(tag);
transaction.commitAllowingStateLoss();
}
And I have got a problem to catch the moment that the fragment is closed by pressing back button. The lifecycle method onStop is not called.
What I should do in order to make it working properly that all lifecycle methods are called?
Hello you can work on activity onBackPressed() like below
#Override
public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() > 1) {
val frag = getSupportFragmentManager().findFragmentById(android.R.id.container);
if (frag is HomeFragment) {
Fragment currentFragment = (HomeFragment) frag;
//do your code
return
}
}
super.onBackPressed();
}
I do the next steps:
Create and open a fragment (fr1) in my main Acitivty.
Replace the fragment for another one (and created too)(fr2)
Pressing button OnBackPressed and navigate to the fr1 (With pop back stack).
4.Trying to replace the fragment(fr1) for the same
fr2 (another time) but my onCreateView is not called.
I read some post this is for lifecycle fragments but did not know how to resolve it.
I need to initialize the view each time that I replace the fragment.
How can I solve it?
public void navigateFragment(Fragment fragment, int fm, String tagFragment) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.fade_in, R.anim.fade_out);
fragmentTransaction.replace(R.id.frame_container, fragment, tagFragment).addToBackStack(tagFragment).commit();
}
public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack();
}
}
I am creating an application with multiple fragments. I have four fragments fragment1, fragment2, fragment3, fragment4. I am moving from different orders like f1 -> f2 -> f4 -> f3 -> f1 or any other order. But when I click the back button from each fragment I need to go to the previous fragment. How to handle this.
Edit 1:
I already tried
FragmentManager fm = ((Activity) context).getFragmentManager();
for (int i = 0; i < fm.getBackStackEntryCount(); i++) {
fm.popBackStack();
}
Which is not help me to solve my issue.
Sample code of Manage Fragment back stack
private Stack<Fragment> stack = new Stack<Fragment>();
public void pushFragments(Fragment fragment, boolean shouldAnimate,
boolean shouldAdd) {
drawerClose = false;
if (shouldAdd)
stack.push(fragment);
this.changeFragment = fragment;
invalidateOptionsMenu();
changeFragment(fragment, shouldAnimate, false);
}
public void popFragments() {
/*
* Select the second last fragment in current tab's stack.. which will
* be shown after the fragment transaction given below
*/
Fragment fragment = stack.elementAt(stack.size() - 2);
// / pop current fragment from stack.. /
stack.pop();
/*
* We have the target fragment in hand.. Just show it.. Show a standard
* navigation animation
*/
this.changeFragment = fragment;
invalidateOptionsMenu();
changeFragment(fragment, false, true);
}
private void changeFragment(Fragment fragment, boolean shouldAnimate, boolean popAnimate) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
if (shouldAnimate)
ft.setCustomAnimations(R.anim.slide_in_right,
R.anim.slide_out_left);
if (popAnimate)
ft.setCustomAnimations(R.anim.slide_in_left,
R.anim.slide_out_right);
ft.replace(R.id.content_frame, fragment);
ft.commit();
}
//On BackPress just check this thing
private void backManage() {
if (stack.size() > 1) {
popFragments();
}
}
Use addToBackStack(String tag), while committing the fragment to add the fragment into the stack of your application:
getFragmentManager().beginTransaction().replace(fragmentContainer.getID(), fragment)
.addToBackStack(null).commit();`
on Activity
#Override
public void onBackPressed() {
if(check_if_backstack_is_null)
super.onBackPressed();
else
{
popupFromBackstack();
}
}
You should override onBackPressed method:
#Override
public void onBackPressed() {
if (fragment != null && fragment.getChildFragmentManager().getBackStackEntryCount() > 0){
fragment.getChildFragmentManager().popBackStack();
}else {
super.onBackPressed();
}
}
For this you can set addToBackStack to fragment transation and then call commit.
By calling addToBackStack(), the replace transaction is saved to the back stack so the user can reverse the transaction and bring back the previous fragment by pressing the Back button.
If you add multiple changes to the transaction (such as another add() or remove()) and call addToBackStack(), then all changes applied before you call commit() are added to the back stack as a single transaction and the Back button will reverse them all together.
You just need to add addToBackStack(null) by FragmentTransaction.
when you are calling next Fragment just add this method with null parameter.
Like this.
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(..............);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
Use this lines of code for it:-
#Override
public void onBackPressed() {
if ( getSupportFragmentManager().getBackStackEntryCount() > 0){
fm.popBackStack();
}else {
super.onBackPressed();
}
}
To get the backStack functionality in your fragmentthan you should have use the .addToBackStack(null) , while performing the fragment transaction like below:-
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.YOUR_CONTAINER, YOUR_FRAGMENT,"TAG")
.addToBackStack(null) /// IT IS NECESSARY TO GET THE BACK STACK PROPERTY IN YOUR FRAGMENT
.commitAllowingStateLoss();
Suppose I'm in fragment A, then moving to B, then using Back button returns to A.
In the activity I'm performing the following override:
#Override
public void onBackPressed(){
FragmentManager fm = getSupportFragmentManager();
Fragment frag = fm.findFragmentByTag(Consts.A);
if (frag != null){
Log.d(Consts.TAGS.ACTIVITY_ORDER,"");
fm.beginTransaction().remove(frag).commit();
fm.popBackStack();
}
}
and while showing B goes like this:
FragmentManager fm = getActivity().getSupportFragmentManager();
Fragment f = BFragment.newInstance(Consts.B);
fm.beginTransaction()
.replace(R.id.rl_content,
f,
Consts.B)
.addToBackStack(null)
.commit();
Now, which method (if any) will be executed in A, once we execute popBackStack()?
If none, how can we change A's data models or UI components (such as keyboard or a TextView) right after back press? is it component-dependent?
R.id.rl_content is the container.
Please consider 2 cases:
1. A is in R.id.rl and being replaced
2. A is not in R.id.rl and is not being replaced
If you're always going back from Fragment B to Fragment A or vice versa, i would recommend this solution inside the fragments themselves.
#Override
public void onResume() {
super.onResume();
Fragment f = AFragment.newInstance(Consts.A);
if(getView() == null){
return;
}
getView().setFocusableInTouchMode(true);
getView().requestFocus();
getView().setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.replace(R.id.rl_content, f);
trans.addToBackStack(null);
trans.commit();
return true;
}
return false;
}
});
}
You can freely move from B to A and A to B using the same code. if you would like a more dynamic approach e.g. you would like to go from Fragment A to Fragment C, or Fragment B to Fragment C and then when you press back go back to the previous fragment on stack. I would aim to use Kyle Falconer's Solution here
Incase the link dies, I'll post the code here:
#Override
public void onBackPressed(){
FragmentManager fm = getFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
Log.i("MainActivity", "popping backstack");
fm.popBackStack();
} else {
Log.i("MainActivity", "nothing on backstack, calling super");
super.onBackPressed();
}
}
I haven't tested the second solution, but use the first.
There are quiet a few ways by which you can change A's data models or UI components.
Case 1: when A is in R.id.rl_content and is being replaced by B. In this case you can simply update required models or UI in onCreateView of Fragment A.
Case 2: When A is not being replaced. In this case fragment A doesn't know when to update its view. In the onBackpressed() of your activity you can call Fragment A's updateView() method if Fragment B is being popped.
#Override
public void onBackPressed(){
FragmentManager fm = getSupportFragmentManager();
FragmentB fragmentB = (FragmentB)fm.findFragmentByTag(Consts.B);
if (fragmentB != null){
Log.d(Consts.TAGS.ACTIVITY_ORDER,"");
fm.beginTransaction().remove(fragmentB).commit();
fm.popBackStack();
FragmentA fragmentA = (FragmentA)fm.findFragmentByTag(Consts.A);
if (fragmentA != null) {
fragmentA.updateView();
}
}
}
EDIT
I understand that you also want to handle scenarios like hiding keyboard etc.
For this you might want to pass backpress event to the individual fragments. Somewhat like this:
#Override
public void onBackPressed(){
FragmentManager fm = getSupportFragmentManager();
FragmentB fragmentB = (FragmentB)fm.findFragmentByTag(Consts.B);
if (fragmentB != null){
if (!fragmentB.onBackPress()) {
// This means fragment B doesn't want to consume backpress therefore remove it.
Log.d(Consts.TAGS.ACTIVITY_ORDER,"");
fm.beginTransaction().remove(fragmentB).commit();
fm.popBackStack();
FragmentA fragmentA = (FragmentA)fm.findFragmentByTag(Consts.A);
if (fragmentA != null) {
fragmentA.updateView();
}
}
}
}
And in your Fragment B create a function onBackPress like this:
public boolean onBackPressed() {
// if keyboard is showing then hide it here and return true to consume the back press event or else return false to dismiss this fragment.
}
I'm wondering which is the proper way to change Fragments, add them to backstack, and restore the visibile Fragment after a screen rotation.
Currently, I use this method to initialize the first Fragment:
private void inflateInitialFragment() {
FragmentManager manager = getFragmentManager();
Fragment mainFragment = manager.findFragmentByTag(MainMenuFragment.class.getSimpleName());
FragmentTransaction ft = manager.beginTransaction();
if (mainFragment == null) {
ft.replace(R.id.mainContainer, new MainMenuFragment(), MainMenuFragment.class.getSimpleName());
} else if (!(mainFragment.isAdded() && !mainFragment.isDetached() && !mainFragment.isRemoving())) {
ft.replace(R.id.mainContainer, mainFragment, MainMenuFragment.class.getSimpleName());
}
ft.commit();
manager.executePendingTransactions();
}
And then to display new Fragments I have methods like this one:
public void openAwards() {
getFragmentManager().beginTransaction().replace(R.id.mainContainer,
new AwardsFragment(), AwardsFragment.class.getSimpleName()).addToBackStack(null).commit();
}
And to go back to the main screen:
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() == 0) {
super.onBackPressed();
} else {
getFragmentManager().popBackStack();
}
}
After a few screen rotations, I've got crashes like this one:
java.lang.IllegalStateException: Fragment already added: MainMenuFragment{42c64d90 #0 id=0x7f0b003f MainMenuFragment}
How should I change the visible Fragments and restore them after a screen rotation?
I don't think that saving some string or Fragment each time is a good solution to restore them.
If your Activity extends android.app.Activity you don't need to override onBackPressed(). It will pop your fragments from back stack automatically.