How to determine if fragment at top of other fragment is removed - android

I have fragment A and adding fragment B in same container (not replacing). I am adding this transaction on backstack also. Now, when device back is pressed, fragment B will be removed and fragment A will become visible. I want to do something when fragment A becomes visible. I searched lot but could not find anything helpful.
Note - I don't want to add backstackchangelistner and call onResume on that fragment.

You can override onHiddenChanged(boolean hidden) in a Fragment.
This will be called when its shown/hidden.
I use the same approach in my app, where i add a fragment and hide the old one, and then use the onHiddenChanged callback when the user presses Back, and the old fragment is shown again.

As you have 2 entries in Back stack, you can check back stack count on
Back Pressed method.
FragmentManager mFragmentManager = getSupportFragmentManager();
int count= mFragmentManager.getBackStackEntryCount();
if(count==1){
// do your work
}

you can try
#Override
public void setMenuVisibility(boolean menuVisible) {
super.setMenuVisibility(menuVisible);
}
or
`fragment.isVisible();`

Related

Closing android application from fragment

I have only one activity in my application with multiple fragments. Whenever a user opens the app, it starts with startupFragment. Whenever user navigate through the fragments and presses back, it takes him back to startupFragment. But when in the startupFragment, I want user, when clicked back, to be able to close the application. Now, here is the code, when the application is started for creating the fragment.:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Set starting fragment
StartupFragment startupFragment = new StartupFragment();
android.support.v4.app.FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().add(R.id.content_main, startupFragment, "startupFragmentTag").commit();
}
As you can see, I have added the tag "startupFragmentTag" to be able to identify it. This is my code onBackPressed:
#Override
public void onBackPressed()
{
Fragment startup = getFragmentManager().findFragmentByTag("startupFragmentTag");
if(startup == null) {
android.support.v4.app.FragmentManager manager = getSupportFragmentManager();
StartupFragment startupFragment = new StartupFragment();
manager.beginTransaction().replace(R.id.content_main, startupFragment, "startupFragmentTag").commit();
} else {
finish();
}
}
So basically what I tried here, is when user is in another fragment, when the user presses back, it will take him/her back to startupFragment. But when in that fragment, when pressing back again, I want the user to be able to exit the application, which won't happen given the code now.
I find the startupFragment by its tag and check if it exists. If not, it will take back the user to it. But if it exists, user should be able to quit, that why finish() is called.
Does the previous fragments not get destroyed? But that shouldn't be the case, because even if I open the app and instantly press back, it won't exit the app.
What am I doing here wrong?
It looks like all you need to do is add each Fragment to the back stack on each FragmentTransaction, and then pop each Fragment from the back stack in onBackPressed().
First, modify onCreate() so that on each transaction, it calls addToBackStack():
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Set starting fragment
StartupFragment startupFragment = new StartupFragment();
android.support.v4.app.FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.add(R.id.content_main, startupFragment, "startupFragmentTag")
.addToBackStack(null)
.commit();
}
Then, in onBackPressed(), just pop from the back stack if you're not on the starting Fragment. If you're on the starting Fragment, and back is pressed, just call super.onBackPressed(), which will exit the app:
#Override
public void onBackPressed() {
android.support.v4.app.FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() > 1) {
//Go back to previous Fragment
fragmentManager.popBackStackImmediate();
} else {
//Nothing in the back stack, so exit
super.onBackPressed();
}
}
You're working with fragments, so to finish your app you need to finish the parent activity.
Try this to finish the activity from startupfragment:
getActivity().finish();
Probably because you've used beginTransaction().add() to add the Fragments including startupFragment and other Fragments on top of it. Then you will always be able to find it using getFragmentManager().findFragmentByTag(), because all Fragments added will be in the Fragment stack (while the find method is actually to find the Fragment with the tag in the stack).
Tips based on what you want to impl:
beginTransaction().replace() will replace instead of adding a Fragment, this way you will only be able to "find" one existing Fragment if you always replace it. i.e. always one Fragment in the stack.
You may want to use getFragmentManager().findFragmentById() to get current showing Fragment (on top of the Fragment stack), instead of findFragmentByTag, if there're several Fragments in the stack which are added not replaced as mentioned above.
When using beginTransaction().add(), you may want to use fragmentManager.addOnBackStackChangedListener() to monitor the Fragment stack changes. Then you probably don't have to handle onBackPressed(). Then in the in the listener you only need to retrieve current Fragment on top stack and see what it is and add your logic there.

Saving Fragment State on Back Button Select

I'm so very confused and have been reading on this topic for a while.
I have a MainActivity that has multiple possible contents (switched between via navigation drawer), which I've set via multiple fragments (lets call them Fragment1, Fragment2 and Fragment3).
That works fine.
One of my fragments, Fragment3, is a View that can segue to a new activity, View2.
View2 has a back button. When I press on the View2 back button I want to see Fragment3 on my MainActivity, not Fragment1, which is what I currently get. This is because OnCreate, by default, loads Fragment1.
What is the best way to do this?
Thanks so much in advance! (vent: iOS makes this so much easier).
The official documentation for Fragments states that you should make sure to call addToBackStack() before commiting your fragment transaction on your first activity holding the 3 fragments.
In order to go back to the last used fragment from the second activity , you should override the onBackPressed() method in this activity :
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
The documentation is very complete on the subject : Read it here
Update:
I just added this to the back button code:
finish();
return true;
I had to do it within onOptionsItemSelected(MenuItem item)... for some reason onBackPressed is not fired.

How to detect if a fragment is presented or hidden?

I am maintaining a backstack of fragments and pop the stack when back button is pressed. I need to reload data every time a fragment is made visible and do some cleanup when it gets hidden. For this I need to detect when a fragment is shown and hidden. This is a very common question but surprisingly none of accepted answers work for me. I
I am adding fragments to backstack using code like this:
public void pushFragment(Fragment f) {
getFragmentManager().beginTransaction()
.add(R.id.content_frame, f, null)
.addToBackStack(null)
.commit();
}
I am popping fragments off using this code:
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 1) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
I am trying to detect from a fragment class when it becomes visible or hidden (either because it is being popped off the stack or another fragment was pushed on top). So far I have tried these callbacks:
onViewCreated/onDestroyView: Only called when the fragment is added to stack and popped off the stack. Not called when the fragment gets hidden or visible because of other fragments on the stack.
onHiddenChanged: Never called. Many have said on SO that this works. But not working for me for some reason.
setUserVisibleHint: Never called
onStart/onPause etc: They don't really apply here because they simply reflect the lifecycle of the host activity.
Is there a Fragment callback that will let me detect when a fragment is being shown or hidden? I will rather not use a backstack listener because I want every fragment class to have its own show/hide logic.
Edit:
If I use replace() to add the fragment (instead of add()) then the view for the previously shown fragment gets destroyed. As a result if that fragment ever to appear on top of the stack again its view is recreated. In this situation onViewCreated/onDestroyView or onStart/onStop will be called every time a fragment is shown or hidden. I suppose I could use that approach. The down side is that the views are created and destroyed frequently. I might as well be using activities instead of fragments for master-detail navigation in that case.
Edit again
If you need a callback you can override onHiddenChanged like this:
public void onHiddenChanged(boolean hidden) {
if(hidden){// Do you stuff here}
}
As PPartisan mentioned you claimed that onHiddenChanged is never called.
The reason for that is because onHiddenChanged doesn't get called the first time an fragment is shown.
Called when the hidden state (as returned by isHidden()) of the fragment has changed. Fragments start out not hidden; this will be called whenever the fragment changes state from that.
To fix this: add this to your code:
FragmentTransaction mFragmentTransaction = getFragmentManager().beginTransaction();
if (f!= null) {
mFragmentTransaction .hide(f);
}
mFragmentTransaction.add(R.id.content_frame, f, null)
mFragmentTransaction.addToBackStack(null)
mFragmentTransaction.commit();
More on the on this SO thread
From the Android documentation:
void onStart () Called when the Fragment is visible to the user. This
is generally tied to Activity.onStart of the containing Activity's
lifecycle.
void onStop () Called when the Fragment is no longer started. This is
generally tied to Activity.onStop of the containing Activity's
lifecycle.
The correct way is using Fragment Lifecycle's methods.
This is get called then the fragment is changing visibility
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {}

Can the views of a fragment be modified when the fragment is in backstack?

I have two fragments which share the same spot on a activity. When activity starts fragA is displayed. When you click a button from fragA it gets pushed to backstack and replaced by fragB. When a button from fragB is pressed the fragB is destroyed and fragA is poped from the backstack but it should also change some views based on what button from fragB was pressed (set the text for a textview for example). I used interfaces for fragment-activity communication. Everything goes well but the views from fragA don't want to change. I think I'm trying to change them while the fragment is in backstack.
#Override
public void onFragBClick(Bundle bundle) {
FragmentManager fragmentManager = getSupportFragmentManager();
fragA = (FragA) fragmentManager.findFragmentByTag(TAG_FRAG_A);
if (fragA != null) {
fragmentManager.popBackStack(TAG_FRAG_A_BACKSTACK,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
fragA.onFragBSignalReceived(bundle);
}
}
This is the method that handles communication. Everything seems fine in logs, onFragBSignalReceived gets called, but I can't change the state of fragA's views in it. My guess is that when it gets called fragA is still on the backstack and that's why views are not updated. In that case how can I achieve this behavior in some other way?
You might want to use popBackStackImmediate() instead of popBackStack(). popBackStackImmediate() will perform the operation inside the method, whereas popBackStack() will queue the action and perform it later on.

Fragment which is not top most in backstack is resumed

Given the application flow show in the graphic and textually described in the following.
Fragment 1 is the lowest fragment but not in the backstack by setting disallowAddToBackStack.
Fragment 2 is pushed onto the stack, using fragmentTransaction.addToBackStack().
A new instance of fragment 1 is pushed onto the stack.
The top most fragment (fragment 1) is popped from the stack.
Activity 2 becomes foreground.
Activity 1 becomes foreground.
Here is the generalized method I use to handle fragments:
private void changeContainerViewTo(int containerViewId, Fragment fragment,
Activity activity, String backStackTag) {
if (fragmentIsAlreadyPresent(containerViewId, fragment, activity)) { return; }
final FragmentTransaction fragmentTransaction =
activity.getFragmentManager().beginTransaction();
fragmentTransaction.replace(containerViewId, fragment);
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if (backStackTag == null) {
fragmentTransaction.disallowAddToBackStack();
} else {
fragmentTransaction.addToBackStack(backStackTag);
}
fragmentTransaction.commit();
}
Problem
When activity 1 resumes in the last step the lowest instance of fragment 1 also resumes. At this point in time fragment 1 returns null on getActivity().
Question
Why is a fragment which is not the top most on the stack resumed?
If resuming the fragment is correct - how should I handle a detached fragment?
When an Activity is not showing UI and then come to show UI, the FragmentManager associated is dying with all of your fragments and you need to restore its state.
As the documentation says:
There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state.
In your Activity onSaveInstanceState and onRestoreInstanceState, try saving you Fragment references and then restore them with something like this:
public void onSaveInstanceState(Bundle outState){
getFragmentManager().putFragment(outState,"myfragment", myfragment);
}
public void onRetoreInstanceState(Bundle inState){
myFragment = getFragmentManager().getFragment(inState, "myfragment");
}
Try this out and have luck! :-)
I don't see how this would happen, unless (based on how you described the steps) you've misunderstood how fragmentTransaction.addToBackStack() works: it manages which transactions are placed in backstack, not fragments.
From the android docs:
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.
So if your step 2 looked something like this in code:
fragmentTransaction.replace(containerViewId, fragment2);
fragmentTransaction.addToBackStack();
fragmentTransaction.commit();
and your step 3:
fragmentTransaction.disallowAddToBackStack()//or just no call to addToBackStack - you do not say
fragmentTransaction.replace(containerViewId, newfragment1);
fragmentTransaction.commit();
At this point, Fragment2 will be removed from the backstack, and your backstack consists of the two Fragment1 instances. in Step 4 you pop the top one, which means you should have the bottommost Fragment1 now at the top.
This explains why it is the resumed fragment if you return to the activity. But not, i'm afraid, why it is apparently detached from its activity.
Android OS can and will create and destroy fragments when it sees fit. This is likely happening when you launch Activity 2 and return to Activity 1. I'd verify for sure that it isn't the actively displayed fragment. What is probably happening is that you are seeing it do some of the creation steps for fragment 1 before it does the creation steps for fragment 2.
As for handling the detached fragments you should take a look at this page. The gist of it is that you should only be using the getActivity in certain fragment functions(Based on the fragment life cycle). This might mean that you have to move some of your logic to other functions.

Categories

Resources