I have Fragment A with RecycleView, I'm replacing it with Fragment B using FragmentTransaction.replace(), then navigating back with popBackStack().
onDestroyView() is called, based on documentation - after onDestroyView() is called, new View will be created next time when the fragment need to be displyed.
Docs:
Called when the view previously created by {#link #onCreateView} has
been detached from the fragment. The next time the fragment needs
to be displayed, a new view will be created.
But, The RecycleView position is preserved, the position is the same when I left this Fragment.
How the RecycleView know his state if new view hierarchy in this fragment was inflated and onSaveInstanceState() wasn't called?
I believe you're trying to understand how an Android Activity "magically" saves (and restores) the transient view hierarchy when it's destroyed, without going and reading the Activity source code where this happens.
Search for onSavedInstanceState and browse the source code. Read the Javadocs.
In particular, start with void onSavedInstanceState(Bundle bundle).
You'll notice it says (and I quote):
(emphasis mine)
The default implementation takes care of most of the UI per-instance state for you by calling {#link android.view.View#onSaveInstanceState()} on each
view in the hierarchy that has an id, and by saving the id of the currently
focused view (all of which is restored by the default implementation of
{#link #onRestoreInstanceState}). If you override this method to save additional
information not captured by each individual view, you will likely want to
call through to the default implementation, otherwise be prepared to save
all of the state of each view yourself.
Additionally, it also says that the saving instance is now always called, because sometimes it's not needed...
One example of when {#link #onPause} and {#link #onStop} is called and not this method is when a user navigates back from activity B to activity A: there is no need to call {#link #onSaveInstanceState} on B because that particular instance will never be restored.
Which makes sense, you will never go back to Activity B (if you do, it will be a new instance with no saved state).
If you're interested in the fine details, you're going to have to clone the Android source code and/or browse it.
I don't know the exact implementation details but it's basically a serialization of a Bundle with primitive view values (the actual implementation would be in the View class since the activity calls each view to do it and stores a bundle "savedInstanceState" with all this information).
The (or one of the) hooks that trigger this behavior is in final void performSaveInstanceState(#NonNull Bundle outState) in the Activity.
The java doc is quite clear: The hook for {#link ActivityThread} to save the state of this activity..
If you look at the actual implementation, notice the call to onSaveInstanceState, and then saveManagedDialogs too, to save any dialog(s) that may be part of the hierarchy.
final void performSaveInstanceState(#NonNull Bundle outState) {
dispatchActivityPreSaveInstanceState(outState);
onSaveInstanceState(outState);
saveManagedDialogs(outState);
mActivityTransitionState.saveState(outState);
storeHasCurrentPermissionRequest(outState);
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
dispatchActivityPostSaveInstanceState(outState);
}
It also stores a bunch of interesting stuff, mActivityTransitionState and storeHasCurrentPermissionRequest. I don't know what exactly these do behind the scenes (and more importantly, how they do it), but it seems like it's somewhat implicit what they do.
If you're interested in more details you're gonna have to dig deeper yourself or hope someone with more free time explains the exact architecture of an Activity. It's an interesting exercise but not for me. It's such a big monolithic class, that I wouldn't want to venture inside of that; i mean, we're talking about a class that has over 8000 lines of code (granted, lots of comments, but still).
There's a lot of hidden complexity in an activity, lots of responsibilities, old java practices, bad and good, a unique coding style, and lots of magic happening behind the scenes by these other "objects".
Now back to your question:
How it has saved state and how it restores state, if onSaveInstanceState() not called?
The Activity calls its internal methods to save the state (in a bundle) as described in the onSaveInstanceState java docs. The class is big and there are plenty of internal/private methods that get called in the process from multiple places, so I do not know the exact architecture of how this is orchestrated (perhaps a Google engineer can try to explain, chances are nobody knows anymore at this point).
An activity will save its state provided the views have an id and it does this by calling each view#onSave... among other things; the state will be restored in onRestoreInstanceState (by the activity) if I correctly recall.
If you look at the javadocs for onRestore... (emphasis mine)
This method is called after {#link #onStart} when the activity is being re-initialized from a previously saved state, given here in savedInstanceState.
Most implementations will simply use {#link #onCreate} to restore their state, but it is sometimes convenient to do it here after all of the initialization has been done or to allow subclasses to decide whether to use your default implementation. The default implementation of this method performs a restore of any view state thathad previously been frozen by {#link #onSaveInstanceState}.
This method is called between {#link #onStart} and {#link #onPostCreate}. This method is called only when recreating an activity; the method isn't invoked if {#link #onStart} is called for any other reason.
So that is how activities do it. They call onSave/onRestore internally for sure.
I found some interesting information in this other stack overflow post.
Related
Android Architecture Components provide the LiveData and ViewModel classes which are more lifecycle-friendly and designed for a leaner Activity/Fragment. These classes handle storing data across configuration changes, but I'm confused about their use compared to the Activity framework APIs. Are onSaveInstanceState(Bundle) and onRestoreInstanceState(Bundle) still necessary or useful for preserving activity state?
onSaveInstanceState & onRestoreInstanceState is still useful.
ViewModel holds data only when process is alive.
But, onSaveInstanceState & onRestoreInstanceState can hold data even if process is killed.
ViewModel is easy to use and useful for preserving large data when screen orientation changes.
onSaveInstanceState & onRestoreInstanceState can preserve data when process is in background.(in background, app process can be killed by system at anytime.)
Assume a scenario :
user is in activity A , then navigates to activity B
but because of low memory Android OS destroys activity A , therefor the ViewModel connected to it also destroys. (You can emulate it by checking Don't keep activities in Developer options)
now user navigates back to activity A, Android OS try's to create new Acivity and ViewModel objects. therefor you loosed data in ViewModel.
But still values in savedInstanceState are there.
As well as the other answers which talk about the ViewModel's persistence beyond simply configuration changes, I think there are a couple more use cases:
Performance reasons
Sometimes you don't want to store all of the latest values of view attributes in the ViewModel for performance reasons. You may have greater need to save them when the view is being re-created. For example, user's scroll position on a view within your activity/fragment. You probably don't want to save the scroll position every time the user scrolls. But you might want to save that onSaveInstanceState so you can restore that when the view is recreated (onRestoreInstanceState).
Initialization to perform after restore
Some views may require initialization especially because of the restore, due to the complex design of those views not being able to save everything. For example, I had a WebView and if the user was in the middle of loading a page during a configuration change, I want the WebView to try to load the new page (rather than the old one). After restoring the state, the observers of LiveData will get the latest values but this doesn't help much with something like this (I only want the view to load a page from the ViewModel at the point of restore, not at other times). So we just do that initialization via the restore state.
Final word
With all this stuff I would advocate keeping your onSaveInstanceState and onRestoreInstanceState as simple as possible. Ideally just call a method on the ViewModel and that's it. Then we can extract all of the logic from the view into the ViewModel, and the view is just left with boilerplate code.
Suppose I have a Fragment A. It has an instance variable mViewPager that points to its ViewPager.
In the onCreate(Bundle) of Fragment A, I invoke setRetainInstance(true).
Upon orientation change:
onCreateView(LayoutInflater, ViewGroup, Bundle) is called, and a new view is inflated. So, I have a new ViewPager inside the newly inflated view.
mViewPager points to the original ViewPager upon orientation change.
My question is: how do I get the new ViewPager in (1) to be associated with the retained mViewPager in (2)?
Or should I just use onSaveInstanceState(Bundle)?
As mentioned in #Selvin's comment, you should let the UI element to be recreated.
Some information which you should know:
setRetainInstance(true) should be used for non-UI Fragment only. And my personal advice would be not to consider this first, unless you are run out of option.
To properly handle a restart, it is important that your activity
restores its previous state through the normal Activity lifecycle, in
which Android calls onSaveInstanceState() before it destroys your
activity so that you can save data about the application state. You
can then restore the state during onCreate() or
onRestoreInstanceState().
You are right about using onSaveInstanceState(Bundle), in general, you should use to save your state. Please be noted that, it is the state you save, but not the UI or the whole Fragment.
For example, a state can be a count on how many times a button is clicked.
Check the link below on how to save state
http://developer.android.com/training/basics/activity-lifecycle/recreating.html#SaveState
Moreover, some UI states, e.g. text inputted in EditText are already handled in the system API. So you only need to handle states you maintained by yourself.
Edit:
If you are new to this, and do not know what you need to save and what do not, simply skip it first, and play around orientation change WITHOUT onSaveInstanceState. Then you would soon find out what is lost in the process, and that would be the state which you need to keep.
I'm using the viewpagerindicator library (http://viewpagerindicator.com/) to create some sort of wizard for my android app. It works fine and does exactly what I want.
I would like to "extend" the functionality a bit by having "previous"/"next" buttons in my ActionBar - pretty much as in Android's "Done Bar" tutorial - to step through the wizard. Works like a charm, too.
HOWEVER:
I would like to display information about the "next" & "previous" fragment in the ActionBar's buttons. Information I pass to the fragments that live in the ViewPager at the time of their "creation" (actually at the time of their object instantiation - using the classical "newInstance(...)" approach to create the instance of my fragment, store the parameters in a Bundle and extract them in the fragment's "onCreate" method). The same way the template does it, when you create a new fragment for your project.
So, this information is the thing I actually want to display in my wizards button to know what fragment is next and which was last.
The type of this information is not important for my problem. It could be a String or an icon or an int or ... anything else you want.
However, wherever I've tried to access my fragments data, the fragment has not yet been fully initialized (meaning its "onCreate" method has not been called yet).
I've tried it in the host fragment's "onViewCreated" method, because I thought that's where all its subviews should be initialized already (at least their "onCreate" method should have been called, I thought), but it seems that this is handled differently for ViewPager to retain only the number of fragments in memory that was set by setOffscreenPageLimit.
So, what I'm looking for (and probably just missing) is the correct callback method here. One that is called when the ViewPager's next Fragments have been loaded and initialized. If such a callback exists, I could place my little piece of code there to update the text in my "previous"/"next" buttons within the ActionBar.
Any help, comments, ideas are highly appreciated. If needed, I can also try to attach some code sample to better explain my setup, but I think it should be easy enough to understand what my problem is.
Thanks in advance!
P.S.: I also tried to do this by using EventBus to send "onFragmentInitialized" messages from my fragments within in the ViewPager and the hosting fragment. It actually worked, but it does not seems the proper way to do this.
When a Fragment's onCreate Method is called, its already preparing to be displayed, and practically its past the point where its considered a Next or Previous fragment instead its considered current.
A fragment's onCreateViews method is called after committing a transaction in the FragmentManager. which takes less than 1 sec to bring it in front of the user (depending on the device and runtime environment)
But in your case, your data should be initalized outside the Fragment that uses it, and displayed where ever you want by passing the data itself then displaying whatever you want form it.
decouple your data from android objects (Fragment, Activity ...) and you should be able to load, maintain, access it cleanly and without worrying about their callbacks.
The Fragment's arguments can be read and loaded in its onAttach callback rather than onCreate, the Activity will then (after onAttach is complete) get a onAttachFragment callback with the Fragment as a parameter. However, I doubt onAttachFragment will be called when switching between already loaded pages in the view pager.
If not, you could have the fragment notify the activity (through an interface) that it is now active during its onActivityCreated, onViewCreated or similar method.
But it sounds more like the activity should register as a page changed listener to the ViewPager itself, and update its state depending on the page rather than which fragment is active.
As a side note, ViewPagerIndicator is quite old now (hasn't been updated in 3 years), a more modern approach is the SlidingTabs example from Google, which has been built into a library available here: https://github.com/nispok/slidingtabs
What is the different between to reinitialize data of Activity in onCreate() and onRestoreInstanceState()
I am getting bundle in oncreate aslo.So My question is why I am not able to set data in oncreate method while the same thing is done in onRestoreInstantstate..Then what is the use of bundle object in oncreate i went through different tutorial but not got relevant answer so please if any one know the difference that where does we use the implementation to reinitialize the data of activity that whether it should be in onCreate or onRestoreInstanceState................please let me know
#numan salati Said:
onRestoreInstanceState is redundant because you can easily restore state in onCreate.
Having said that here is what the official doc says for
onRestoreInstanceState:
Most implementations will simply use onCreate(Bundle) to restore their
state, but it is sometimes convenient to do it here after all of the
initialization has been done or to allow subclasses to decide whether
to use your default implementation.
So for best practice, lay out your view hierarchy in onCreate and
restore the previous state in onRestoreInstanceState. If you do that,
anyone who subclasses your Activity can chose to override your
onRestoreInstanceState to augment or replace your restore state logic.
This is a long way of saying onRestoreInstanceState serves as a
template method.
Here
I'm having some trouble understanding how to make a simple DialogFragment to edit a (complex) object, say a Person, with first and last name, and a list of e-mail addresses each consisting of an enum (Work, Home, etc) and the address.
First of all, how do I properly pass the Person object to a DialogFragment? My current solution has a setPerson(Person person) method, that's called after my DialogFragment is created, but before dialog.show(). This works ok, until a configuration change happens (user rotates the screen). The DialogFragment gets recreated and the reference to my Person object is null. I know I can save the instance using onSaveInstanceState, but the object is complex and expensive, and persisting a large object this way seems wasteful.
I've also tried disabling configuration change in the activity that uses my dialog, and that fixes the problem, but I want the dialog to be reuseable and requiring all the activities that use it to disable configuration changes seems wrong.
Third option would be to save the reference to Person in a static variable, but again, I want the dialog to be reuseable and able to support multiple instances.
How do other people handle their expensive and complex objects in reuseable dialogs?
Well, there are several solutions, none of which are fantastic or failsafe if you are completely unable to serialize the object you're editing.
I don't recommend ever using android:configChanges="orientation" unless it's absolutely, 100% unavoidable. There are other configuration changes, and your app will still break with the others if you resort to using that solution.
But a simple solution that will work in the vast majority of cases is to call setRetainInstance(true) on the DialogFragment. This will prevent your Fragment from being destroyed and re-created on a configuration change. There is an edge-case where this might not work, though. There are other reasons besides configuration changes where the OS will attempt to put an activity or app 'on ice', for example to save memory. In this case, your object will be lost.
The cleanest way to pass a complicated Object to a fragment is to make the Object implement Parcelable, add the object to a Bundle, and pass the bundle to the Fragment with fragment.setArguments(bundle). You can unpack the Object in onActivityCreated() of the fragment by retrieving the bundle through a call to getArguments().
To persist the argument on configuration changes, simply save the "working" parcelable Object to the bundle provided by onSaveInstanceState(Bundle state) method of the fragment, and unpack the argument later in onActivityCreated() if savedInstanceState !=null.
If there is a noticeable performance hit from implementing Parcelable, or you have a "live" object of some kind, one option is to create a non-UI fragment to hold the data object. Without getting into details, you can setRetainInstance(true) on the non-UI fragment and coordinate the connection with the UI fragment through interfaces in the Activity.