Does fragment onCreateView need to account for restore? - android

This is really a two questions pertaining to lifecycle.
1) In Fragment.onCreateView(LayoutInflater, ViewGroup container, Bundle), all of the examples I've seen simply use the LayoutInflater to inflate a View, and return it. If this is part of a Restore though, i.e. non-null Bundle, shouldn't the restoration of the view hierarchy be handled by the system? Should I instead be calling container.findViewById() or trying to pull the view out of the Bundle or something (for the purposes of pulling out references to subviews)?
2) In general, do any of the Fragment lifecycle callbacks need to worry about saving/restoring the state of its view hierarchy, even implicitly by calling super.onXXX()? Or is that all handled uplevel by the owning Activity when it calls super.onCreate(Bundle)?

Although the framework is responsible for re-creating the Fragment itself, the view hierarchy must be recreated manually. Views cannot survive the destruction of their Activity (plus, since onCreateView() has your implementation, you could conditionally inflate another layout or do different things -- that's why it has to run every time). The Bundle contains information put there by onSaveInstanceState(), but the old views are not part of it.
If the view ids match between the old and new layout, then the state should be automatically restored (via the super calls). Views override their own onSaveInstanceState() for this. If you save your custom state in the fragment's onSaveInstanceState(), then you're also responsible for restoring it.

Related

How RecycleView know his state when navigate back to replaced Fragment

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.

Lifetime of presenter in Mosby 3 - MVP

I've migrated to Mosby 3.0.3 and now investigating abilities of PresenterManager.
For me is not completely clear the lifetime of presenter regarding to Activity, Fragment and ViewGroup.
I've looked through ViewGroupMvpDelegateImpl, FragmentMvpDelegateImpl and ActivityMvpDelegateImpl and couldn't 100% be sure when presenter is terminated.
As far as I understand by default keepPresenterInstance and keepPresenterOnBackstack are always true and we have such situation:
Fragment's presenter exists until it's fragment is removed from backstack or the whole process is terminated. In this case I rely on method from FragmentMvpDelegateImpl:
protected boolean retainPresenterInstance() {
Activity activity = getActivity();
if (activity.isChangingConfigurations()) {
return keepPresenterInstanceDuringScreenOrientationChanges;
}
if (activity.isFinishing()) {
return false;
}
if (keepPresenterOnBackstack && BackstackAccessor.isFragmentOnBackStack(fragment)) {
return true;
}
return !fragment.isRemoving();
}
But what about Fragments in childFragmentManagers or about Fragments from FragmentPagerAdapter (also inside another fragment)?
Activity's and ViewGroup's presenters exist until theirs's activity isn't finished or the whole process is terminated. In this case I rely on method from ActivityMvpDelegateImpl:
static boolean retainPresenterInstance(boolean keepPresenterInstance, Activity activity)
{
return keepPresenterInstance && (activity.isChangingConfigurations()
|| !activity.isFinishing());
}
Is this true for all ViewGroups, wherever they are resided?
The reason of asking such question is that in our app we have activities, fragments, fragments inside fragments, custom viewgroups and all of them have presenters.
The idea is to release huge resources in presenter in detachView(final boolean retainInstance) but keep in memory something lightweight - like inMemory cache, to reuse it when view will be restored from backstack.
Likewise we use Dagger2 and it's important to understand when to release appropriate subcomponent.
Activity: Presenter gets destroyed (removed from Presentation Manager and therefore can be garbage collected) when Activity gets finished (or on Activity Process death). So activity keeps presenter during screen orientation changes and also on the back stack (if you have multiple activities on the back stack). Works pretty the same as in Mosby 2.x
Fragments: In Mosby 2.x Presenter were only kept across screen orientation changes if you set Fragment.setRetainInstanceState(true). This is not required anymore in Mosby 3.x because Mosby 3.x uses PresenterManager to keep presenter across orientation changes. Also, since Mosby 3.x Presenter will be kept if fragment is on the back stack. That means, even if the Fragment has no View because Fragment is on the back stack so UI widgets has been destroyed (Fragment.onDestroyView() has been called), Presenter instance will be kept and if the user pops the Fragment back stack so that you come back the presenter is reused. However, while Fragment is on the back stack, View is detached from Presenter (presenter.detachView(true) will be called) and gets reattached after Fragment is the one that is on top of fragment back stack. This is the default behavior and works for any kind of Fragment incl. ViewPager and Nested ChildFragments on (child)back stack. You can configure this as constructor parameter of FragmentMvpDelegateImpl(keepPresenterInstance, keepPresenterOnBackstack) to don't keep presenter at all during screen orientation change or to don't keep presenter while fragment on the back stack.
ViewGroups: In mosby 2.x they didn't survive screen orientation changes at all. In Mosby 3.x they do, because they use PresenterManager. However, you have to give each ViewGroup a id (#id/something) otherwise Android Framework dont know how to map views (portrait view to landscape view). As you have already observed correctly, ViewGroups are kept until "host activity" dies or ViewGroup gets removed from parent ViewGroup programmatically like parentViewGroup.removeChild(indexOfViewGroup). Doesn't matter where it resides. Please note that it may or may not work well if you put ViewGroup in a Fragment. I haven't thought / tested that edge case yet. Still it will take "host activity" into account (and not the Fragment as parent), however, it might be the case that if a Fragment will be put on the back stack (and push a new Fragment) that the View of the Fragment containing your ViewGroup will be removed which is similar to programmatically calling parentViewGroup.removeChild(indexOfViewGroup) (maybe FragmentManager internally calls this) so that the presenter of your ViewGroup will be destroyed but it might shouldn't (because Fragment user can pop backstack so that fragment). In that case it is likely that ViewGroup creates a new Presenter instance. As already said, I haven't tested nor thought about this edge case yet, but there is no way (as far as I know) to get a reference to the "hosting Fragment" since Fragment is not known to ViewGroup. I recommend to either use Mosby Fragments or Mosby ViewGroups but not Mosby ViewGroup in a Fragment (prefer a child fragment then if really necessary).
You can configure all that behavior by setting boolean flag in corresponding Delegate constructor.
The easiest way to detect if that works as expected is to add logs to createPresenter() methods and persenter.detachView() method. Also Mosby provides some logging for debugging. You can enable that in each delegate i.e. FragmentMvpDelegateImpl.DEBUG = true (for example call this in Application.onCreate()). Please note you have to do that for each delegate (Activity, Fragment, ViewGroup and also for each variant Mvp, Mvp+ViewState, Mvi). You can also enable logging for PresenterManager in the same way if you want to.

why mostly only onCreateView() is being used in most of fragment examples not onViewCreated() [duplicate]

What's the essential difference between these two methods? When I create a TextView, should I use one over the other for performance?
Edit:
What's the difference from
onCreateView() {
root = some view
View v = new View(some context);
root.add(v);
return root;
}
onViewCreated() {
View v = new View(some context);
getView().add(v);
}
We face some crashes initializing view in onCreateView.
You should inflate your layout in onCreateView but shouldn't initialize other views using findViewById in onCreateView.
Because sometimes view is not properly initialized. So always use findViewById in onViewCreated(when view is fully created) and it also passes the view as parameter.
onViewCreated is a make sure that view is fully created.
onViewCreated android Documentation
Called immediately after onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle) has returned, but before any saved state has been restored in to the view. This gives subclasses a chance to initialize themselves once they know their view hierarchy has been completely created. The fragment's view hierarchy is not however attached to its parent at this point.
onViewCreated is called immediately after onCreateView (the method you initialize and create all your objects, including your TextView), so it's not a matter of performance.
From the developer site:
onViewCreated(View view, Bundle savedInstanceState)
Called immediately after onCreateView(LayoutInflater, ViewGroup, Bundle) has returned, but before any saved state has been restored in to the view. This gives subclasses a chance to initialize themselves once they know their view hierarchy has been completely created. The fragment's view hierarchy is not however attached to its parent at this point.
Source: Fragment#onViewCreated
It's better to do any assignment of subviews to fields in onViewCreated. This is because the framework does an automatic null check for you to ensure that your Fragment's view hierarchy has been created and inflated (if using an XML layout file) properly.
Code snippet from: FragmentManger.java
// This calls onCreateView()
f.mView = f.performCreateView(f.getLayoutInflater(f.mSavedFragmentState), null, f.mSavedFragmentState);
// Null check avoids possible NPEs in onViewCreated
// It's also safe to call getView() during or after onViewCreated()
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
if (f.mHidden) f.mView.setVisibility(View.GONE);
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
onCreateView() is the Fragment equivalent of onCreate() for Activities and runs during the View creation.
onViewCreated() runs after the View has been created.
should I use one over the other for performance? NO. There's no evidence of a performance boost.
There is actually an onCreate() method in Fragments too, but it's rarely used (I do never use it, nor find a good use case for it).
I always use onCreateView() in Fragments as a replacement for onCreate().
And I'm happy with that.
The docs for Fragment.onCreateView() now says:
It is recommended to only inflate the layout in this method and move logic that operates on the returned View to onViewCreated(View, Bundle).
No need for us to understand why; we just need to do as the docs says, but it would be interesting to know why this recommendation exists. My best guess is separation of concern, but IMHO this makes it a little bit more complicated than it has to be.
onCreateView returns the inflated view. OnViewCreated is called just after onCreateView and get has parameter the inflated view. Its return type is void
Ok, so If we are going to talk about onCreateView() and onViewCreated(). It is worth while to talk a little about the fragment lifecycle. The full documentation about the fragment lifecycle can be found HERE, I recommend that you read up on it, as we will only be talking about the states relevant to onCreateView() and onViewCreated().
The fragment lifecycle contains 5 states:
1) INITIALIZED
2) CREATED
3) STARTED
4) RESUMED
5) DESTROYED
The reason that we need to talk about the fragment lifecycle is because both onCreateView() and onViewCreated() get called during the CREATED state in the lifecycle.
So when a fragment is instantiated, it begins in the INITIALIZED state, for example when you see:
CustomFragment frag1 = new CustomFragment() //`INITIALIZED` state
CustomFragment.class //`INITIALIZED` state
The .class syntax is the class literal syntax and for a brief summary I would recommend reading the blog post HERE
For a fragment to transition into other lifecycle states, it must be added to a Fragment Manager.
The Fragment Manager is responsible for determining what state its fragment should be in and then moving them into that state.
Difference between onCreateView() and onViewCreated()
Once the fragment has been added to the Fragment Manager, onAttach() is called to attach the fragment to the host activity.
Once onAttch() is called the fragment enters the CREATED state. It is in this state that the Android system begins creating the fragment's view. This can be done a few ways, for example the documentation states:
In most cases, you can use the fragment constructors that take a #LayoutId, which automatically inflates the view at the appropriate time. You can also override onCreateView() to programmatically inflate or create your fragment's view
If we look at the documentation for onCreateView() we see that:
It is recommended to only inflate the layout in this method and move logic that operates on the returned View to onViewCreated
Conclusion
Now combining everything we can come to the conclusion, both onCreateView() and onViewCreated() are called during the CREATED state of a fragment's life cycle. However, onCreateView() is called first and should only be used to inflate the fragment's view. onViewCreated() is called second and all logic pertaining to operations on the inflated view should be in this method.
i think the main different between these is when you use kotlin.in onCreateView() every Time you want to access to view in your xml file you should use findViewById but in onViewCreated you can simply access to your view just by calling the id of it.
The main reason I would use onViewCreated is since it separates any initialization logic from the view hierarchy inflation/creation logic which should go in the onCreateView . All other performance characteristics look the same.
In the Google Documentation
There is a new solution to declare the xml resource in the Fragment superclass
class ExampleFragment : Fragment(R.layout.example_fragment) { }
Then bind the view in onViewCreate
private lateinit var binding : ExampleFragmentBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = ExampleFragmentBinding.bind(view)
}
In this way you do not need to inflate view in onCreatedView or handle the view in onDestroy
onCreateView is used in fragment to create layout and inflate view.
onViewCreated is used to reference the view created by above method.
Lastly it is a good practice to define action listener in onActivityCreated.

Are UI element ID's in a fragment automatically able to be accessed by the parent activity?

I can't seem to find any use cases of this, and Android Developers isnt being too clear about it. I get that to access an activities UI elements from a Fragment I need to call getActivity.findViewById, and to access the fragments from an Activity its getFragment.findFragmentById. But for the activity, can i just call findViewById if the Fragments is attached to the activity without calling anything else? Essentially, does an Activity automatically know the view IDs of contained Fragments UI elements.
findViewById() simply traverses the layout to find a view with that ID. When a Fragment is attached to an Activity, it is added to that Activity's layout. So yes, you can access a Fragment's views via their IDs from an Activity, but I would highly recommend against it. Add a public method on your Fragment that lets you make whatever change to those views that is desired, instead. This way you have the freedom to change your layout without worrying about breaking the host Activity.
Also, you don't need to use getActivity().findViewById() within a Fragment -- simply override onViewCreated(), and call findViewById() on the provided view instance. Or at any point in the lifecycle between onCreateView() and onDestroyView() you can use getView().findViewById().
For example:
public void onViewCreated(View view, Bundle savedInstanceState) {
TextView fragmentTextView = (TextView) view.findViewById(R.id.textview_in_fragment);
}

Difference between onCreateView and onViewCreated in Fragment

What's the essential difference between these two methods? When I create a TextView, should I use one over the other for performance?
Edit:
What's the difference from
onCreateView() {
root = some view
View v = new View(some context);
root.add(v);
return root;
}
onViewCreated() {
View v = new View(some context);
getView().add(v);
}
We face some crashes initializing view in onCreateView.
You should inflate your layout in onCreateView but shouldn't initialize other views using findViewById in onCreateView.
Because sometimes view is not properly initialized. So always use findViewById in onViewCreated(when view is fully created) and it also passes the view as parameter.
onViewCreated is a make sure that view is fully created.
onViewCreated android Documentation
Called immediately after onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle) has returned, but before any saved state has been restored in to the view. This gives subclasses a chance to initialize themselves once they know their view hierarchy has been completely created. The fragment's view hierarchy is not however attached to its parent at this point.
onViewCreated is called immediately after onCreateView (the method you initialize and create all your objects, including your TextView), so it's not a matter of performance.
From the developer site:
onViewCreated(View view, Bundle savedInstanceState)
Called immediately after onCreateView(LayoutInflater, ViewGroup, Bundle) has returned, but before any saved state has been restored in to the view. This gives subclasses a chance to initialize themselves once they know their view hierarchy has been completely created. The fragment's view hierarchy is not however attached to its parent at this point.
Source: Fragment#onViewCreated
It's better to do any assignment of subviews to fields in onViewCreated. This is because the framework does an automatic null check for you to ensure that your Fragment's view hierarchy has been created and inflated (if using an XML layout file) properly.
Code snippet from: FragmentManger.java
// This calls onCreateView()
f.mView = f.performCreateView(f.getLayoutInflater(f.mSavedFragmentState), null, f.mSavedFragmentState);
// Null check avoids possible NPEs in onViewCreated
// It's also safe to call getView() during or after onViewCreated()
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
if (f.mHidden) f.mView.setVisibility(View.GONE);
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
onCreateView() is the Fragment equivalent of onCreate() for Activities and runs during the View creation.
onViewCreated() runs after the View has been created.
should I use one over the other for performance? NO. There's no evidence of a performance boost.
There is actually an onCreate() method in Fragments too, but it's rarely used (I do never use it, nor find a good use case for it).
I always use onCreateView() in Fragments as a replacement for onCreate().
And I'm happy with that.
The docs for Fragment.onCreateView() now says:
It is recommended to only inflate the layout in this method and move logic that operates on the returned View to onViewCreated(View, Bundle).
No need for us to understand why; we just need to do as the docs says, but it would be interesting to know why this recommendation exists. My best guess is separation of concern, but IMHO this makes it a little bit more complicated than it has to be.
onCreateView returns the inflated view. OnViewCreated is called just after onCreateView and get has parameter the inflated view. Its return type is void
Ok, so If we are going to talk about onCreateView() and onViewCreated(). It is worth while to talk a little about the fragment lifecycle. The full documentation about the fragment lifecycle can be found HERE, I recommend that you read up on it, as we will only be talking about the states relevant to onCreateView() and onViewCreated().
The fragment lifecycle contains 5 states:
1) INITIALIZED
2) CREATED
3) STARTED
4) RESUMED
5) DESTROYED
The reason that we need to talk about the fragment lifecycle is because both onCreateView() and onViewCreated() get called during the CREATED state in the lifecycle.
So when a fragment is instantiated, it begins in the INITIALIZED state, for example when you see:
CustomFragment frag1 = new CustomFragment() //`INITIALIZED` state
CustomFragment.class //`INITIALIZED` state
The .class syntax is the class literal syntax and for a brief summary I would recommend reading the blog post HERE
For a fragment to transition into other lifecycle states, it must be added to a Fragment Manager.
The Fragment Manager is responsible for determining what state its fragment should be in and then moving them into that state.
Difference between onCreateView() and onViewCreated()
Once the fragment has been added to the Fragment Manager, onAttach() is called to attach the fragment to the host activity.
Once onAttch() is called the fragment enters the CREATED state. It is in this state that the Android system begins creating the fragment's view. This can be done a few ways, for example the documentation states:
In most cases, you can use the fragment constructors that take a #LayoutId, which automatically inflates the view at the appropriate time. You can also override onCreateView() to programmatically inflate or create your fragment's view
If we look at the documentation for onCreateView() we see that:
It is recommended to only inflate the layout in this method and move logic that operates on the returned View to onViewCreated
Conclusion
Now combining everything we can come to the conclusion, both onCreateView() and onViewCreated() are called during the CREATED state of a fragment's life cycle. However, onCreateView() is called first and should only be used to inflate the fragment's view. onViewCreated() is called second and all logic pertaining to operations on the inflated view should be in this method.
i think the main different between these is when you use kotlin.in onCreateView() every Time you want to access to view in your xml file you should use findViewById but in onViewCreated you can simply access to your view just by calling the id of it.
The main reason I would use onViewCreated is since it separates any initialization logic from the view hierarchy inflation/creation logic which should go in the onCreateView . All other performance characteristics look the same.
In the Google Documentation
There is a new solution to declare the xml resource in the Fragment superclass
class ExampleFragment : Fragment(R.layout.example_fragment) { }
Then bind the view in onViewCreate
private lateinit var binding : ExampleFragmentBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = ExampleFragmentBinding.bind(view)
}
In this way you do not need to inflate view in onCreatedView or handle the view in onDestroy
onCreateView is used in fragment to create layout and inflate view.
onViewCreated is used to reference the view created by above method.
Lastly it is a good practice to define action listener in onActivityCreated.

Categories

Resources