I'm learning how to use fragments properly.
I have 4 fragments:
A
B
Loading
Complete
It should replace each other in this sequence:
A ==> Loading ==> B ==> Loading ==> Complete
Method that change my Fragment A into Loading:
LoadingFragment loadingFragment = new LoadingFragment();
Bundle bundle = new Bundle();
bundle.putBoolean("fragmentA", true);
loadingFragment.setArguments(bundle);
getFragmentManager().beginTransaction()
.replace(R.id.fragment_container, loadingFragment)
.addToBackStack(null)
.commit();
Changing Fragment B into Loading looks similarly (only arguments are changed obviously)
Method that change Loading fragment into Fragment B:
new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
getFragmentManager().beginTransaction()
.replace(R.id.fragment_container, new FragmentB())
.commit();
}
}).start();
Thread.sleep(...) is just a long operation...
Obviously I'm not adding .addToBackStack(null) this time.
Ok. Now the problem:
When I use getFragmentManager().popBackStack() method, the previous fragment (A) is added below (my fragment container is LinearLayout) my actual fragment instead of replaceing it. According to this: https://stackoverflow.com/a/17793959/3279023 it should work, but it's not.
The behavior you are experiencing looks OK to me. popBackStack() reverses the last saved operation, which in your case is: replace A with Loading.
So it replaces Loading with A, but that has no effect on B obviously, which you added without recording the transaction.
A solution could be using addToBackStack() in both cases, so that both transaction are registered, and then call popBackStack() twice.
Several fragments can make one activity/screen. Backstack is like back arrow or go back to the last activity/screen.
Related
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.
I’m developing an android application that makes heavy use of fragments, I’m running into an issue and I’ve been unable to find a solution so far.
The flow is this: the app is launched and MainActivity is the first responder, now, depending on user interaction several fragments gets loaded and pushed onto the stack.
Here is an example:
Main Activity -> fragment A -> fragment B -> fragment C -> etc..
Back history is enabled like so:
fragment C -> fragment B -> fragment A -> etc..
Everything works perfectly fine as long as my application is in foreground but everything breaks when the application goes in background.
If I’m on fragment B for example and I press the home button the application goes in background and when I restore it back it starts from MainActivity with fragment A.
Also, the toolbar shows the title of fragment B and, since fragment A contains a recyclerview I can see parts of fragment B between item rows, like a background image.
This is how I load fragments:
public void loadFragment(Fragment fragment, Boolean addToStack) {
// Load fragment
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.frame_container, fragment);
fragmentTransaction.addToBackStack(null);
// show back button
if (addToStack) {
// Code to show the back button.
}
else if (fragmentManager.getBackStackEntryCount() > 0 && !addToStack) {
hideBackButton();
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
fragmentTransaction.commitAllowingStateLoss();
}
I call this function from MainActivity and from the fragments:
MyFragment theFragment = new MyFragment();
MainActivity.instance.loadFragment(theFragment, true);
What I want to achieve is that when the application is restored it gets straight to the previously loaded fragment, keeping the entire "back" history. How can I do this?
I'm not sure if other portions of code are needed, but if so I'll post them as required.
call the onResume() function:
public void onResume(){
Fragment frg = null;
frg = getSupportFragmentManager().findFragmentByTag("Your_Fragment_TAG");
final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.detach(frg);
ft.attach(frg);
ft.commit();
}
Ok so, after a lot of research I found the issue… and the issue was a mistake of mine.
I’d like to report it here for everyone that might run into the same issue.
At first I tried to force fragment replacement on onResume() function like so:
Fragment f = getSupportFragmentManager().findFragmentById(R.id.container);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.frame_container, f);
fragmentTransaction.addToBackStack(null);
However android should handle all of this automatically, at least in my knowledge, and so I tried to dig further and I finally narrowed it down to my onStart() method.
Basically I was registering the EventBus and making a function call
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().register(this);
UserNetworkManager userNetworkManager = new UserNetworkManager(MainActivity.mainActivity);
userNetworkManager.fetchFeed();
}
This code was creating the issue and after all it was not necessary to put it there, so I moved it to the onCreate() method, cleaned up my code a little bit and everything works fine now.
I have an activity with a container for fragments and a NavigationDrawer. If I select an item in the drawer, I call updatePage(index).
I also call updatePage(0) if I create the activity and the savedInstanceState == null to init my activity.
One fragment has a sub fragment and therefor it just replaces itself by the subfragment and adds the subfragment to the backstack, so that the user can navigate back to the previous fragment. In this fragment I call following code directly:
#Override
public void onClick(View view)
{
Event event = (Event)view.getTag();
FragmentManager fm = getActivity().getSupportFragmentManager();
GamesFragment f = new GamesFragment();
Bundle bundle = new Bundle();
bundle.putParcelable(GamesFragment.KEY_EVENT, event);
f.setArguments(bundle);
FragmentTransaction ft = fm.beginTransaction()
.replace(R.id.frame_container, f, f.getClass().getName())
.addToBackStack(null);
ft.commit();
}
Why does sometimes the removing of the old fragment not work? I get overlaying fragments, but only sometimes.
My activities updatePage function looks like following:
private void updatePage(int drawerSelection)
{
mDrawer.closeDrawer();
Fragment f = null;
FragmentManager fm = getSupportFragmentManager();
switch (drawerSelection)
{
case 0:
f = fm.findFragmentByTag(HomeFragment.class.getName());
if (f == null)
f = new HomeFragment();
break;
case 1:
f = fm.findFragmentByTag(EventFragment.class.getName());
if (f == null)
f = new EventFragment();
break;
default:
break;
}
if (f != null && !f.isAdded())
{
// SOLUTION:
// Backstack clearen
// fm.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.frame_container, f, f.getClass().getName());
ft.commit();
}
}
I saw such situation few times and I bet that it can happen after replacing with addToBackStack. Easiest fix is to add background to fragment layout. From android doc:
Note: When you remove or replace a fragment and add the transaction to the back stack, the fragment that is removed is stopped (not destroyed). If the user navigates back to restore the fragment, it restarts. If you do not add the transaction to the back stack, then the fragment is destroyed when removed or replaced.
This view is not redrawed entirely just overdrawed and if second fragment doesn't has background or view container redraw you will get effect as described. That's my theory :>. Sometimes Android documentation is not as clear as we would like it to be.
The problem is following use case:
Going to the EventFragment from the GameFragment pushes the "remove(EventFragment).add(GameFragment)" transaction to the backstack. If I now press the back button, this transaction will be undone and everything is fine. The backstack is empty again and everything works. BUT, if I don't press the back button, but change to another fragment through the menu, the backstack does still have the above mentioned transaction. Pressing back will now try to undo this transaction... It will just readd the EventFragment before the code in my menu click handler adds the fragment from the menu... This is how it could happen...
Easy solution, if I only want one backstack for every menu entry and want to delete the backstack, if I select another area of my app in the menu is, to clear the backstack before I go to another area through the menu...
So adding ' fm.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);' before replacing the the current fragment will solve the problem (I added this solution as a comment to my main post)
I am trying to make a shared element transition between fragments, everything works fine when using replace() to add the second fragment, however in the codebase add() is used a lot, but when using that, transition just skips to end values
Is it possible to have the transition between added fragments?
Thanks
#Override
public void onClick(View v) {
setSharedElementReturnTransition(TransitionInflater.from(getActivity())
.inflateTransition(android.R.transition.move));
FragmentB secondFragment = new FragmentB();
secondFragment.setSharedElementEnterTransition(TransitionInflater.from(getActivity())
.inflateTransition(android.R.transition.move));
getFragmentManager().beginTransaction()
.add(R.id.container, secondFragment)
.addToBackStack(null)
.addSharedElement(imageView, imageView.getTransitionName())
.commit();
}
Try this
getSupportFragmentManager().beginTransaction()
.addSharedElement(myImage, "mytransition")
.add(R.id.recycler_view_container, myFragment2)
.hide(myFragment1)
commit();
worked for me
since the system isnt going through the onPause from the first fragment its not going to happen. becuase when you add a new fragment, the new fragment comes on the top of the old fragment.
but you can fake it though you will have more code !
there is a sample below:
https://github.com/Kisty/FragmentTransitionExample
and a video not compeletely related but helps you to get the idea:
https://www.youtube.com/watch?v=CPxkoe2MraA
Try add .detach() method for FragmentTransaction.
FragmentManager manager = activity.getSupportFragmentManager ();
Fragment currentFragment = manager.findFragmentById (CONTAINER_ID);
int intoContainerId = currentFragment.getId ();
manager.beginTransaction ()
.setTransition (FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.addSharedElement(view, transitionName)
.addToBackStack (withTag)
.detach(currentFragment)
.add(intoContainerId, newFragment, withTag)
.commit();
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();
}
}