I'm confused in a fundamental Android fragment lifecycle.
When I press back, none of my nested fragments are saved and the app recreates everything in a parent activity. These nested fragments are inside viewpagers.
Now, if I press the menu button, and come out of the app, it is stored.
Why does it happen? And how do I ensure that even if the user comes out of the app, by pressing back, the nested fragment states are saved, and restored when the user opens the app again.
There is a fragment state pager adapter for viewpager's to save the state of fragments. But you can still not change the fragment life cycle which is dependent on activity life cycle. So if an activity's on destroy is called, all its fragments will be destroyed. So, this might be your case.
Related
Is it normal behaviour that onDestroyView() is called immediately when you navigate to a different fragment?
I call the following method to navigate:
findNavController().navigate(R.id.action_homefragment_to_detailsfragment)
Is there a way to avoid this? Or should everything be restored from a ViewModel? For example my last scroll position in a ScrollView..
Fragments on the back stack have their views destroyed, but the view state is saved and automatically restored when it comes to the top of the stack again (i.e., you hit the system back button).
Only views with an android:id have their state saved and restored, so make sure any important views have an id.
CONSTRUCTION
I've got Activity that holds a "path" made of Fragments.
The User goes from Fragment1 to Fragment2 and then to Fragment3 filling up the required informations. Nothing fancy here just plain backStack and .replace made on Fragments.
QUESTION
How should I retain that information when orientation changes to get my backStack back the way it was before orientation change?
IDEAS
The only idea I have is to insert into saveInstanceState the last visible Fragment and recreate things from there but it feels really hacky and I think in the long run it's going to make some major problems.
/////////////UPDATE//////////////
It looks like nowadays Android is capable of doing it on its own as long as you are following the guidelines provided by ARTICLE
The backstack itself is saved and loaded automatically with the activity.
Here's an article how Android does save and load activity and fragment states.
What you should care of is implementing the same logic of saving and restoring states for each fragment.
My situation: I have two ListFragments (call them A and B) managed by one Activity which keeps persistent references to both of these Fragments. When I click a button in Fragment A, I replace that with Fragment B. The problem starts when I do the following flow.
A -> B -> (scroll) -> (back button) -> B
In that case, when I go back to Fragment B the second time, the previous scroll position is maintained, which I don't want. Instead, I would like for Fragment B to start with its ListView at the top of its content.
Things I have tried which do nothing:
Calling setSelection(0) in onActivityCreated
Calling setSelectionAfterHeaderViews() in onActivityCreated
Calling smoothScrollToPosition(0) in onActivityCreated
Interestingly, all of these work if I post them on a Runnable. However, when I do that there is a weird flickering the second time I open Fragment B.
So, how do I get Fragment B to automatically scroll to the top each time it is attached to its parent Activity? I feel like there must be something blindingly obvious that I'm missing, but I'm really stumped right now.
You're calling the right methods, but you're calling them in the wrong place.
I assume you have code that switches between the fragments and you call it when an item is clicked in A. So whenever you do the switch set the scroll to the top, something along these lines:
protected void switchList() {
ListFragment a = (ListFragment) getFragmentManager().findFragmentByTag("a");
ListFragment b = (ListFragment) getFragmentManager().findFragmentByTag("b");
b.getListView().setSelectionAfterHeaderView();
getFragmentManager().beginTransaction().hide(a).show(b).addToBackStack(null).commit();
}
And one important note: never keep persistent references to fragments in your activities. Whenever you need a fragment get it from the FragmentManager. This is crucial since on configuration change (like a device rotation, or when your app is suspended and restored) the fragments are recreated, and the reference you kept leads to a 'dead' fragment. Not only is it a major leak, it will also prevent your code from functioning. any change you make to the saved fragment is not reflected on the screen because the screen holds the newly created fragment.
Background:
I have a main Activity, it wraps a main Fragment that can be changed, and in order to keep a backstack I use FragmentManager's backstack.
The main difference from keeping an activity stack is that when a fragment is pushed to the backstack and get replaced it will call it's onDestroyView() but not it's onDestroy(), and when it get back it's view will be re-created with onCreateView(). (however onCreate() is not called as the fragment object is not disposed)
In an activity stack it won't happen and the views remain.
This has a positive effect on low-end devices as the Android OS can free some memory and you don't have to keep the views right (in my app messages from the server might change the view in any time) so one can save precious bandwidth as well.
The Actual Problem:
Let's say I have a fragment and the user click on something and it's view is changed, e.g. a list is expanded.
If the user then go to another screen (i.e. fragment) the previous fragment will be pushed to the backstack and it's view will be destroyed.
When the user is going back, the fragment will be re-created and will not "remember" the changes the user had made, e.g. the list would not be expanded as it should
so how can I save the state and restore it without making special cases for every view?
Undesired Answers:
keep the view alive: doing something to keep the view would break the fragment efficiency
using onSaveInstanceState(): it will not get called when the fragment is pushed to the backstack as the activity is not destroyed and that's not a configuration change.
special object: prefer not to do it if there is a way the system can do it for you.
I have looked around and found variations of this question, but all of the answers seem to be ugly hacks. Is there no easy and proper way to achieve this?
Say for instance I have Activity A, which has a FrameLayout that can hold one fragment at a time. Lets say that when the Activity is first started it loads Fragment A into the FrameLayout, which consists of a ListView. When an item is selected in Fragment A it starts up Fragment B. The way I am currently doing this is by simply hiding Fragment A and then adding Fragment B since this preserves Fragment A's state. I am of course also adding this fragment transaction to the backstack.
So now Fragment A exists on the backstack. Say now I go back to my Android home screen and go to another app. While I am doing this, the Android system decides to destroy my application because the system needs more memory. When I navigate back to my application, how am I supposed to properly restore the state of Fragment B, and Fragment A which is currently on the backstack.
I cannot use setRetainInstance() since it does not work for Fragments placed on the backstack.
Essentially what I am trying to do is restore the backstack to exactly how it was before my application was forcefully closed. So Fragment A should be on the backstack (but not showing), and Fragment B should be currently showing. If I hit the back button, it should properly pop Fragment A off the backstack.
Some notes
Since the application was forcefully closed, it's savedInstanceState != null. Same thing holds for the fragments.