Android fragment simple backstack - android

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.

Related

Back From fragment B to Fragment A not working Kotlin

I have two fragments in my Kotlin code.
When I'm pressing some of the buttons then the First fragment will inflate the second fragment.
The second fragment is displayed and all works fine but when I'm pressing the back button then the Phone is going to the Home page (The application is minimized), when I click on the Recently viewed apps that open all the Opened applications on the screen and choosing my Application (that is opened) then the application got back to Fragment Alike its suppose to be.
But I don't understand why the application is minimized when I'm clicking on the back button?
I just want it to go back to fragment A and do not minimize the application.
This is the code to inflate the second fragment:
val fragment2 = details_frag()
val fragmentManager: FragmentManager? = fragmentManager
val fragmentTransaction: FragmentTransaction =
fragmentManager!!.beginTransaction()
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
fragmentTransaction.apply {
replace(R.id.fragSec, fragment2)
commit()
}
} else {
fragmentTransaction.apply {
replace(R.id.flFragment, fragment2)
commit()
}
}
The Code in the Main Activity that inflate the first fragment is:
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
supportFragmentManager.beginTransaction().apply {
replace(R.id.fragLand, firstFrag)
commit()
}
supportFragmentManager.beginTransaction()
.add(
R.id.fragSec,
details_frag::class.java,
null
) // .addToBackStack(null)
.commit()
} else {
supportFragmentManager.beginTransaction().apply {
replace(R.id.flFragment, firstFrag)
commit()
}
}
I don't see something unusual here, it all works just great but it's just minimize my app when I'm going from the second fragment to the first fragment...
(The first is inserted inside the Main Activity like you can see and I just swap the first fragment with the second one when someone clicks on something in my code...)
Thank you!!!
You need to call addToBackstack in the FragmentTransaction (like you're doing in the second example). That makes the current transaction you're performing (replacing fragment A with B) a new state on the stack. Then when the user hits back, that state can be popped off and the transaction reversed, so you're back with fragment A in place.
If you don't add the transaction to the backstack, it becomes part of the most recent state, which is when you added A - that state becomes "added A and then replaced it with B", and when you pop that off, it goes back to "before you added A"

Call a method on Popbackstack()

I replace Fragment-A with Fragment-B, and on calling popbackstack() in Fragment-B i will redirect back to Fragment-A. So I wanted to call a method that will update the list.
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.
In Short for your use case, you need to use add and if you don't want the fragment views to overlap then you might replace the Fragment B view background color with a solid color instead of transparent.
When you add your fragment, you also have to add it to your fragment's backstack.
FragmentTransaction ft = getSupportFragmentManager.beginTransaction();
ft.replace(R.id.content, fragment, backStateName);
ft.addToBackStack(backStateName);
ft.commit();
(here R.id.content is your fragment container in your activity)
Then you have to override onBackPressed method in your activity that contains your fragments.
#Override
public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() > 1) {
getSupportFragmentManager().popBackStack();
}
}
if you want to customize your onBackPressed then you can check for your currently visible fragment in your activity and create a callback for performing action for that particular fragment. example -
#Override
public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() > 1) {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.content);
if (fragment instanceof FragmentB) {
((FragmentB) fragment).onBackPressed();
// getSupportFragmentManager().popBackStack();
}
}
now you can add getSupportFragmentManager().popBackStack(); in your Fragment's onBackPressed() method.

Fragment and BackStack not working properly

My MainActivity contains a Navigation Drawer with two fragments. The first fragment is loaded automatically when the app starts. I want the app to switch from fragment two to fragment one when the back button is pressed or if the first fragment is added then exit the app. I am adding the fragmentTransaction of the first fragment to a stack and then calling popBackStack in my onBackPressed method. However the behaviour is pretty weird.
When I'm on the first fragment the application should exit (ie, execute super.onBackPressed) however when on the first fragment and pressing back the first fragment is removed from the fragmentholder leading to a blank screen and then on pressing the second time the app closes.
When I'm on the second fragment nothing happens when the back button is pressed the first time and on pressing the back button a second time the app closes. Here's the relevant code from the MainActivity.java
#Override
public void onBackPressed() {
if (musicService.isPng()) moveTaskToBack(true);
if (getFragmentManager().getBackStackEntryCount() > 0)
getFragmentManager().popBackStack("returnFragment", 0);
else super.onBackPressed();
}
private void loadSelection(int i) {
navList.setItemChecked(i, true);
switch (i) {
case 0:
FirstFragment firstFragment = new FirstFragment();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragmentholder, firstFragment)
.addToBackStack("returnFragment")
.commit();
break;
case 1:
SecondFragment secondFragment = new SecondFragment();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragmentholder, secondFragment)
.commit();
break;
case 2:
musicService.removeNotification();
musicService.stopSelf();
MainActivity.this.finish();
}
}
loadSelection(0) is called in the onCreate of the MainActivity
Unlike given in the code, I have tried various modes of implementing the popBackStack() method but all of them lead to the same result. Just to add I don't want to implement a workaround since there are only 2 fragments since I am already working on adding new fragments.
try this,
call method .addToBackStack("returnFragment") while loading second fragment and remove it from first transaction.

Returning back to an Activity after calling and closing a Fragment does not change my Activity's lifecycle

Given an Activity that acts as a Home page (it never closes) that launches various fragments, how to know when the Activity is visible to the user?
From what I have observed, when I open a fragment the lifecycle for the Activity never changes, onPause() is not called. And when I close the fragment, onResume() is not called on my Activity.
Here is how I am starting my fragments, I am using this method and passing the fragment I want to launch to it.
public void addFragment(int containerId, Fragment fragment, boolean addToBackStack) {
// Check if the fragment has been added already. If so, then
// don't add the fragment.
Fragment temp = mFragmentManager.findFragmentByTag(fragment.getClass().getName());
if(temp != null && temp.isAdded()) {
return;
}
FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.add(containerId, fragment, fragment.getClass().getName());
if(addToBackStack)
ft.addToBackStack(null);
ft.commit();
}
What is the methodology for indicating that my Activity is visible again? Thanks in advance!
in the oncreate method of your home activity, call
mFragmentManager.addOnBackStackChangedListener(this) ;
and then define
#Override
public void onBackStackChanged() {
int backStackCount = mFragmentManager.getBackStackEntryCount();
if(backStackCount == 0) {} //back to home screen
}
Your Activity is always Visible even if thousand Fragments are showing at the same time, for the sake of understanding Fragments are just Custom-Views, and the Fragment gives a helping hand in handling your View, so onPause() on your activity does not need to called when a Fragment dies or is born,just like inflating a View.
Just like Sir #Tim Mutton said, you need to check your BackStack to know if you are back, or you can use the ViewGroup method ViewGroup.indexOfChild(View child) - this method will an int of value getChildCount()-1 which means its on top of its fellow sibblings..
Hope it helps

Android: keep Fragment running

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();
}
}

Categories

Resources