Hi I want to keep a Fragment alive even if it is not shown anymore. Because I have some AsyncTasks going there.
Firstly I am adding a starting point Fragment
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.fragmentMenu, menuFragment);
fragmentTransaction.commit();
Later on I replace menuFragment with the Fragment which should stay alive
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.fragmentMenu, btFragment).addToBackStack(null).commit();
Lastly I override the onBackPressed() method for using popBackStack()
#Override
public void onBackPressed() {
if(getFragmentManager().getBackStackEntryCount() != 0){
getFragmentManager().popBackStackImmediate();
}else{
super.onBackPressed();
}
}
Inside my btFragment which should stay alive even if I pop another Fragment I have set setRetainInstance(true) inside the onCreate() method.
But it is getting destroyed as soon as I pop the backstack.
Am I doing something wrong? thx
The documentation for setRetainInstance() explains that it applies only to activity recreation:
Control whether a fragment instance is retained across Activity
re-creation (such as from a configuration change)
I solved it with a little bit of a workaround. Here is the code if anyone is interested in it.
// little work-around to not let btFragment die.
Fragment fragment = getFragmentManager().findFragmentById(R.id.fragmentMenu);
if (fragment instanceof BTFragment) {
getFragmentManager().beginTransaction().hide(fragment).add(R.id.fragmentMenu, menuFragment).commit();
} else if (getFragmentManager().getBackStackEntryCount() != 0) {
getFragmentManager().popBackStackImmediate();
} else {
super.onBackPressed();
}
Basically I'm hiding the Fragment instead of removing it and by checking the FrameLayout for the current Fragment I can handle different Fragments. With this way, the onDestroy() method is not called on my btFragment.
Related
My app contains one empty activity and a couple of fragments. The onCreate of the activity replaces the empty view in activity_main.xml with a MainFragment that contains some buttons. Each button launches a separate fragment, and user can navigate from one fragment to another, etc.
On the press of back key, the current fragment correctly gets replaced with the previous fragment, until you get to the MainFragment. When user presses back from MainFragment, it hides the main fragment and you see the white empty background of the main activity. But I want to exit from the activity at this point, as that would be the sensible behaviour.
I am able to achieve this by calling super.onBackPressed() for a second time from onBackPressed if there are no fragments left in the fragment manager.
#Override
public void onBackPressed() {
super.onBackPressed();
FragmentManager manager = getSupportFragmentManager();
List<Fragment> fragments = manager.getFragments();
if (fragments == null || fragments.size() == 0) {
Log.d(TAG, "No more fragments: exit");
super.onBackPressed();
}
}
Is this acceptable thing to do - would it create any issues in the activity workflow? Is there a better/standard way to handle this scenario?
There is no problem to do that, but probably it would be easier if when you add the main fragment to the activity you do NOT call .addToBackStack()
You don't really need to override onBackPressed in your Activity. I would suggest implementing a method for adding fragments in your Activity:
protected void addFragment(Fragment fragment, boolean addToBackStack) {
String tag = fragment.getClass().getName(); //It's optional, may be null
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction()
.add(R.id.your_container_id, fragment, tag);
if (addToBackStack) {
transaction.addToBackStack(tag);
}
transaction.commit();
}
And modify your onCreate method of activity like in the following snippet:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
// Add your fragment only if it is a first launch,
// otherwise it will be restored by system
addFragment(new YourFirstFragment(), false);
}
}
For all other fragments use:
addFragment(new OtherFragment(), true);
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.
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
I am developing an application which is based on Fragment and main activity implements the Slide navigation. I have got three fragment."A", "B", "C" Let say I traverse from "B" fragment to an independent activity. When I try to return from Activity, it lands me up on "A" Fragment where as I want to return to same fragment from where I traversed to activity.
I am using the below code to transact with Fragments
if (fragment != null) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.container_body, fragment);
fragmentTransaction.commit();
}
How to do this?
check it out my below code,
#Override
public void onBackPressed() {
super.onBackPressed();
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() == 1) {
//your code goes here
} else if (fm.getBackStackEntryCount() > 1) {
fm.popBackStack();
//your code goes here
} else {
fm.popBackStack();
//your code goes here
}
}
count return number of fragment you have crossed, comment it if you need any help
The problem you're dealing with pretty common in Android and they've explained in a good article in the docs.
So basically, you want to use the BackStack but for your specific issue, there is another solution: when you're moving to the other activity, the original one (which contains B) is basically not destroyed so, what you could do is put states in this activity (the one containing A, B and C) and, as soon as, you come back from your independant activity, your onResume will be fired and you just have to override it to make it display the fragment you want.
Does the Back Stack support interaction with nested Fragments in Android?
If it does, what am I doing wrong? In my implementation, the back button is completely ignoring the fact that I added this transaction to the back stack. I'm hoping it is not because of an issue with nested fragments and just me doing something incorrectly.
The following code is inside of one of my fragments and is used to swap a new fragment with whatever nested fragment is currently showing:
MyFragment fragment = new MyFragment();
FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.setCustomAnimations(R.animator.slide_in_from_right, R.animator.slide_out_left, R.animator.slide_in_from_left, R.animator.slide_out_right);
ft.addToBackStack(null);
ft.replace(R.id.myFragmentHolder, fragment);
ft.commit();
I have the same problem, I would like to nest fragments, and to keep a back stack for each nested fragment.
But... it seems that this case is not handled by the v4 support library. In the FragmentActivity code in the library, I can find :
public void onBackPressed() {
if (!mFragments.popBackStackImmediate()) {
finish();
}
}
The mFragments represents the FragmentManager of the activity, but it does not seem this manager "propagates" the pop to children managers.
A workaround would be to manually call the popBackStackImmediate() on the child manager, like this in the activity inherited from FragmentActivity :
private Fragment myFragmentContainer;
#Override
public void onBackPressed() {
if (!myFragmentContainer.getChildFragmentManager().popBackStackImmediate()) {
finish(); //or call the popBackStack on the container if necessary
}
}
There might be a better way, and a more automated way, but for my needs it is allright.
In my current project we have multiple "nested layers" so I've come up with following workaround to automatically pop backstack only for top level fragment managers:
#Override
public void onBackPressed() {
SparseArray<FragmentManager> managers = new SparseArray<>();
traverseManagers(getSupportFragmentManager(), managers, 0);
if (managers.size() > 0) {
managers.valueAt(managers.size() - 1).popBackStackImmediate();
} else {
super.onBackPressed();
}
}
private void traverseManagers(FragmentManager manager, SparseArray<FragmentManager> managers, int intent) {
if (manager.getBackStackEntryCount() > 0) {
managers.put(intent, manager);
}
if (manager.getFragments() == null) {
return;
}
for (Fragment fragment : manager.getFragments()) {
if (fragment != null) traverseManagers(fragment.getChildFragmentManager(), managers, intent + 1);
}
}
As of API 26 there is a setPrimaryNavigationFragment method in FragmentTransaction that can be used to
Set a currently active fragment in this FragmentManager as the primary navigation fragment.
This means that
The primary navigation fragment's child FragmentManager will be called first to process delegated navigation actions such as FragmentManager.popBackStack() if no ID or transaction name is provided to pop to. Navigation operations outside of the fragment system may choose to delegate those actions to the primary navigation fragment as returned by FragmentManager.getPrimaryNavigationFragment().
As mentioned by #la_urre in the accepted answer and as you can see in FragmentActivity's source, the FragmentActivity's onBackPressed would call its FragmentManager's popBackStackImmediate() which in turn would check whether there is an mPrimaryNav (primary navigation, I assume) fragment, get its child fragment manager and pop its backstack.
#Override
public void onBackPressed() {
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
return;
}
finish();
}