0

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?

Pavel Poley
  • 5,307
  • 4
  • 35
  • 66
  • I think if a recycler view has id it saves its state in bundle implicitly. An EditText works like this – Ilya Mashin Dec 17 '20 at 12:20
  • Views with an `id+/..` are automatically saved by the Android framework and restored (where applicable). If a view has no `id` it is not saved (afaicr). – Martin Marconcini Dec 17 '20 at 16:30
  • @MartinMarconcini so `onCreateView()` is called, new view hierarchy inflated, new `RecycleView` object created and restores state based on `id`? but why the restored state triggered on back press? – Pavel Poley Dec 17 '20 at 17:20
  • I'm not sure I understand your question. Have you read [Google's official documentation](https://developer.android.com/topic/libraries/architecture/saving-states) about saving states? Also you may want to take a look [at Activity.java's source code](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Activity.java) to get a better idea where it starts. In short, if there's a saved state, it will be restored, so when you hit back, you go back to an activity whose state was saved by the framework. You can elect to finish() the activity if you don't want this. – Martin Marconcini Dec 18 '20 at 09:13
  • @MartinMarconcini the example is about fragments with single activity. `so when you hit back, you go back to an activity whose state was saved by the framework` - this is something new? activity restores state when you navigate back? I think you wrong – Pavel Poley Dec 18 '20 at 09:59
  • The restored state is for the incoming activity, not the leaving one. When you hit back, your current activity is destroyed and finished(), no state is saved (or preserved) in that case, but if your last activity/fragment has a saved state, it will be restored (given all the rules/constraints). – Martin Marconcini Dec 18 '20 at 10:30
  • How it has saved state and how it restores state, if `onSaveInstanceState()` not called? – Pavel Poley Dec 18 '20 at 11:04

1 Answers1

1

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.

Martin Marconcini
  • 26,875
  • 19
  • 106
  • 144
  • 1
    Thanks for your time! Tried digging in the source code, it turn out that `FragmentManager` calls `fragmentStateManager.saveViewState()` and it's internally saves the view hierarchy state of the fragment. but `View.onSaveInstanceState()` is not invoked in this story, instead `View.dispatchSaveInstanceState`. seems like `View's` saves their state between fragments transactions too (in addition to `Activity.onSaveInstanceState()`) – Pavel Poley Dec 18 '20 at 14:07
  • 1
    Good additions, as you can see, _nothing is as simple as it seems_ (when it comes to Android). In any case, I totally forgot about "fragments" in the mix... which -as you have seen - is a bigger story! – Martin Marconcini Dec 18 '20 at 16:15