I have a 3 fragments:
Activity Base -> Fragment A ->Fragment B -> Fragment C
Navigation is correct between these fragments but when I do:
Activity Base -> Fragment A ->Fragment B -> Fragment C -> Activity X
I can't get a correct navigation. When I press back on Activity X it goes to Fragment A (Activity Base) and doesn't go to Fragment C.
Any ideas? Thx
Activity Base should use onSaveInstanceState to store the current active fragment. Then it has to restore that fragment in either onCreate (if the Bundle parameter is not null), or in onRestoreInstanceState.
Note (from the documentation)
Most implementations will simply use onCreate(Bundle) to restore their state, but it is sometimes convenient to do it [in onSaveInstanceState] after all of the initialization has been done or to allow subclasses to decide whether to use your default implementation
Related
I'm facing an issue concerning the use of navigation component.
I have this hierarchy :
Activity A composed of fragment f1 and fragment f2
Activity B
When I'm on f2, I change activity like this :
button.setOnClickListener {
startActivity(Intent(activityA, ActivityB::class.java))
}
What I would like is : When I go to activity B, the fragment f2 (of activity A) should be removed. So, when I will kill the activity B, I will come back to activity A without fragment f2.
To illustrate the flow :
Activity A (f1 > f2) > Activity B (Need to remove f2)
When I will come back : Activity B > Activity A (f1 only)
I'm using navigation component and I have tried to use the popUpTo and popUpToInclusive options in my nav_graph. I managed to remove the fragment but there is always a glitch when I remove it (i.e. we see the removal of fragment before passing the activity B, so we see the f1 fragment a little time). I would like to make the transaction invisible to user.
Is there a way to do it ? Don't hesistate to ask me if you need more precisions.
Thanks for your answers,
When you're using the navigation component, you can have direction, then use the navigation component to move from origin fragment/activity to destination fragment/activity. For example:
val actionLockToLogin = LockFragmentDirections.actionLockToLogin()
NavHost.findNavController(this).navigate(actionLockToLogin)
In this code, I go from LockFragment, to LoginFragment. Just add the fragments or activities in the navigation's XML. Set an Action from origin fragment / activity to destination fragment / activity. and by using the code I show you, try to navigate between them.
I am using navigation controller component. let say I have 4 fragments
A --> B --> C or D
from fragment A, it can only go to fragment B. but if we are in fragment B, it can go to fragment C or to fragment D.
I want to perform method workXYZ() in onResume fragment B if it comes from fragment A, and I want to perform method doSomethingABC() in onResume fragment B if it comes from fragment C or fragment D.
from fragment A to fragment B I use the code below:
val BDestination = FragmentADirections.actionToFragmentB()
Navigation.findNavController(fragmentView).navigate(BDestination)
to reach fragment C and fragment D from fragment B, I use global action. because fragment C & D are actually not only used by fragment B. so I use the code like below to go to fragment C or D
val CDestination = BFragmentDirections.actionGlobalFragment()
Navigation.findNavController(fragmentView).navigate(CDestination)
and from fragment C or fragment D, I use back button to be back to fragment B.
I have tried to use safe arguments boolean comesFromFragmentA = true in navigation graph in order to give a sign that fragment B comes after fragment A. but unfortunately, that value on safe arguments boolean comesFromFragmentA will remain the same (true) if it comes from fragment C or D.
so what should I do if want to know if the fragment B appears after previous fragment or from the fragment afterward ?
Fragment arguments are mutable so you can change them at any time. Therefore you can update that argument value right before you navigate to signify that future onResume() calls will be after you return to this Fragment:
// Set the comesFromFragmentA argument to signify that the next onResume()
// will be when you come back to this Fragment
arguments.putBoolean("comesFromFragmentA", false)
// Now navigate
val CDestination = BFragmentDirections.actionGlobalFragment()
Navigation.findNavController(fragmentView).navigate(CDestination)
I've a fragment A. I add() it with tag like this:
fragmentTransaction.addToBackStack(special_tag);
Then I simply add() fragment B on top of fragment A. After that, I decide to remove fragment B and go back to fragment A using:
activity.fragmentManager.popBackStackImmediate(special_tag, 0)
When I reach the fragment A, it seems that fragment doesn't re-run it's lifecycle methods: onAttach(), onResume(), onCreate() ect.
Can someone explain this behavior and maybe suggest an alternative?
(I need to "refresh" the data when I come back to fragment A second time)
What is causing this result?
Is there a clean solution/work-around?
Update
Fragment B is GuidedStepFragment and does not have a .replace() function. I found that it has finishGuidedStepFragments(), but it behaves the same (it does not call fragment life cycle functions)
Situation (again):
Fragment A (Simple fragment) -> .add(Fragment B) (GuidedStepFragment) -> popBackStackImmediate() or finishGuidedStepFragments()
I add Fragment B like this:
GuidedStepFragment.add(activity.fragmentManager, fragmentB.createInstance())
Using fragmentTransaction.add(Fragment) doesn't remove Fragment A. What is actually happening is that Fragment A is still running behind Fragment B. Since Fragment A never stopped running, it's lifecycle has no need to retrigger.
Consider using fragmentTransaction.replace(Fragment) and replace the fragment in the container (fragment A) with fragment B. If you pop that transaction from the back stack, then Fragment A will reattach and follow your expected lifecycle.
Update
Since you seem to be using GuidedStepFragments from the leanback library, this is a little tricky. GuidedStepFragment actually performs replace(...) under the hood, but you're adding fragment B to a different container so the original behavior I mentioned doesn't apply.
I'm not super familiar with leanback (since it's usually only used for android tv), but I do know that you can at least do the following. If you keep track of your backstack size, when all of the GuidedStepFragments have been popped, you will have returned to your original fragment. For example, let's assume your backstack starts at zero:
activity.fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if (activity.fragmentManager.getBackStackEntryCount() == 0){
// handle your updates
}
}
});
// the next line of code will add an entry to the backstack
GuidedStepFragment.add(activity.fragmentManager, fragmentB.createInstance());
// eventually when back is pressed and the guided fragment is removed, the backstack listener should trigger
CONSTRUCTION
Activity A holds Fragment A that is able add Fragment B on backstack.
PROBLEM
Fragment B holds Views generated via API response.
State of these Views is what I need to be able to recreate after rotation or when going back to Fragment A by using onBackPressed and lunching Fragment B again.
I've read quite some of the topics on SO about Fragments in backstack and I am aware of their inability to retain instance.
What should I do to achieve such outcome?
Fragments on backstack can always retain instance if you save it. An Activity or a Fragment on a backstack in simply in a paused state. So you want to save the data/variables in the onSaveInstanceState method for that class (you will be overriding it).
Now to restore from the saved state you would have noticed that the onCreate, onCreateView for Activity, Fragment, respectively, have a Bundle savedInstanceState parameter being passed in. This is where you saved your state in the previous step, thus, you can add
if (savedInstanceState != null) {
//TODO: restore the state
}
to your onCreate/onCreateView method and you should be good to go.
I have followed the ABS example of FragmentTabsPager, but I'm facing this problem:
Activity A has two fragments: F1, F2 which are added in onCreate() method of the A with use of the TabsAdapter.
Activity A has the option to refresh fragments which is done according to
http://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity
Activity A has the option to launch Activity B, which adds item to F1 (F1 thus, needs to be updated)
However, after finishing B, the fragments are not attached to A anymore. After some debugging, I found that the onCreate() method of A is called after finishing B with savedInstanceState containing this entry:
android:support:fragments=android.support.v4.app.FragmentManagerState$41bfffe0
My understanding is that FragmentActivity saved the fragments before launching B and after relaunching A, it tries to restore them. With a little of tracing I found that after finishing B, these actions are called:
Activity A: constructor called
Fragment F1: constructor called
Fragment F2: constructor called
Fragment F1: onAttach
Fragment F1: onCreate
Fragment F2: onAttach
Fragment F2: onCreate
Activity A: onCreate
Activity A: TabsAdapter created
TabsAdapter: addTab android.widget.TabHost$TabSpec#41d949f8
Fragment F1: constructor called
TabsAdapter: addTab android.widget.TabHost$TabSpec#41d965d0
Fragment F2: constructor called
Fragment F1: onActivityCreated
Fragment F1: onCreateLoader
Fragment F2: onActivityCreated
Fragment F2: onCreateLoader
Activity A: onStart
Fragment F1: onResume
Fragment F1: onCreateLoader
Fragment F2: onResume
Fragment F2: onCreateLoader
Activity B: onStop
Fragment F1: onLoadFinished
Fragment F2: onLoadFinished
There are 2 instances of each fragment, and the showing fragments are not attached to activity.
How can I solve this? What am I doing wrong? Maybe if there was a way how to get the restored fragments?
Thanks in advance
If I am understanding this correctly, here is what I would suggest:
Since Fragments F1 and F2 live in Activity A, Activity B should not create, or attempt to interact with either Fragment.
Have Activity A start Activity B using startActivityForResult, and have B pass back the item (to be added to Fragment F1) to A.
Activity A should handle sending the new item to Fragment F1 using an interface (like you've done).
Upon receiving the new item (in your interface method) from Activity A, Fragment F1 should then update its data and refresh its Views.
Another thing to note, ensure that your Activity A only adds your Fragments once in onCreate, it can use the savedInstanceState to know if it is being recreated or if it's the first creation of it. See http://developer.android.com/training/basics/fragments/fragment-ui.html.
Edit
Ah now I see more of what you are asking. There are several ways you can go here.
Use the FragmentManager and the tag trick to get the Fragments out of the ViewPager, and update them as needed.
This is slightly more dangerous as it will return Fragments that may not be in onResume and need to be checked before being interacted with.
Decouple the model from the controller, as in put your data into a static singleton, SQLite DB, or even preferences. This would allow other portions of the app to access and edit the data, without the need of passing it around.
In onResume, your Fragments would then need to check and update their data, in case it had been changed.
Your Activity will also need to give them a refresh signal via an interface method if the Fragments are already in onResume when the change occurs.
OR, use a Loader in your Fragments to monitor the data set and have it autoupdate the Fragment's UI via interface callbacks.