We utilize LiveData's observe method quite a bit in our fragments. A recent release of the androidx fragment sdk is leading to Android Studio marking instances of liveDataObject.observe(this) as incorrect in favor of liveDataObject.observe(getViewLifecycleOwner()) .
Added a new Lint check that ensures you are using getViewLifecycleOwner() when observing LiveData from onCreateView(), onViewCreated(), or onActivityCreated(). (b/137122478)
https://developer.android.com/jetpack/androidx/releases/fragment
We are worried about implementing this change because we do not understand how the functionality of getViewLifecycleOwner() compares to that of using this, and whether it will cause a conflict with the use of this or this.getActivity() when setting up the ViewModel in the fragment.
Additionally, we use the Android Navigation component and noticed that when the user navigates to different fragments within the same activity, each fragment's onDestroyView() method is called, but not onDestroy()
Here is an example of our code in onViewCreated()
vm.getStemLengths().observe(this, stemLengths -> {
this.stemLengths = new ArrayList<>(Stream.of(stemLengths).map(stemLength ->
new SearchModel(Integer.toString(stemLength.getValue()))).toList());
});
Later, in onDestroyView()
vm.getStemLengths().removeObservers(this);
At the same time, depending on the fragment, the ViewModel that houses the LiveData is set up with one of the following:
vm = new ViewModelProvider(this.getActivity()).get(PrepareVM.class);
To persist the viewmodel across fragments in the activity.
Or:
vm = new ViewModelProvider(this).get(AprobacionVM.class);
If the VM does not need to be persisted outside of the current fragment
So to summarize, will changing this to getViewLifeCycleOwner() when observing LiveData objects in fragments' onCreateView() conflict with the ViewModel patterns / Navigation component? Could there be an instance for example where the livedata change ends up triggering an observer from a previous fragment in the same activity that a user navigates away from?
From the documentation of getViewLifeCycleOwner it seems that making this change may allow us to remove the removeObservers() call in each fragment's onDestroyView(). Is that the correct understanding?
Fragment implements LifecycleOwner which maps its create and destroy events to the fragment's onCreate and onDestroy, respectively.
Fragment.getViewLifecycleOwner() maps its create and destroy events to the fragment's onViewCreated and onDestroyView, respectively. The precise sequence is described here.
If you're working with views in your observers, you'll want the view lifecycle. Otherwise you might get updates when the view hierarchy is invalid, which may lead to crashes.
From the documentation of getViewLifeCycleOwner it seems that making this change may allow us to remove the removeObservers() call in each fragment's onDestroyView().
Correct.
There is already a bug with it. If you do a new Intent for a fragment already use it gives getView is null error. Why? The activity is newly creared.
Related
I find Fragment#setRetainInstance(true) confusing. Here is the Javadoc, extracted from the Android Developer API:
public void setRetainInstance (boolean retain)
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being re-created.
onAttach(Activity) and onActivityCreated(Bundle) will still be called.
Question: How do you as a developer use this, and why does it make things easier?
How do you as a developer use this
Call setRetainInstance(true). I typically do that in onCreateView() or onActivityCreated(), where I use it.
and why does it make things easier?
It tends to be simpler than onRetainNonConfigurationInstance() for handling the retention of data across configuration changes (e.g., rotating the device from portrait to landscape). Non-retained fragments are destroyed and recreated on the configuration change; retained fragments are not. Hence, any data held by those retained fragments is available to the post-configuration-change activity.
It's very helpful in keeping long running resources open such as sockets. Have a UI-less fragment that holds references to bluetooth sockets and you won't have to worry about reconnecting them when the user flips the phone.
It's also handy in keeping references to resources that take a long time to load like bitmaps or server data. Load it once, keep it in a retained fragment, and when the activity is reloaded it's still there and you don't have to rebuild it.
Added this answer very late, but I thought it would make things clearer. Say after me. When setRetainInstance is:
FALSE
Fragment gets re-created on config change. NEW INSTANCE is created.
ALL lifecycle methods are called on config change, including onCreate() and onDestroy().
TRUE
Fragment does not get re-created on config change. SAME INSTANCE is used.
All lifecycle methods are called on config change, APART FROM onCreate() and onDestroy().
Retaining an instance will not work when added to the backstack.
Don't forget that the above applies to DialogFragments as well as Fragments.
The setRetainInstance(boolean) method is deprecated, use ViewModels instead.
The setRetainInstance(boolean) method on Fragments has been deprecated as of Version 1.3.0 of fragment API.
With the introduction of ViewModels, developers have a specific API for retaining state that can be associated with Activities, Fragments, and Navigation graphs. This allows developers to use a normal, not retained Fragment and keep the specific state they want retained separate.
This ensures that developers have a much more understandable lifecycle for those Fragments (one that matches all of the rest of their Fragments) while maintaining the useful properties of a single creation and single destruction (in this case, the constructor of the ViewModel and the onCleared() callback from the ViewModel).
As of 2019, I'm trying to follow a best practice on where to start observing LiveData in Fragments and if I should pass this or viewLifecycleOwner as a parameter to the observe() method.
According to this Google official documentation, I should observe in onActivityCreated() passing this (the fragment) as parameter.
According to this Google sample, I should observe in onViewCreated() passing viewLifecycleOwner as parameter.
According to this I/O video, I shouldn't use this but instead viewLifecycleOwner, but doesn't specify where should I start observing.
According to this pitfalls post, I should observe in onActivityCreated() and use viewLifecycleOwner.
So, where should I start observing? And should I either use this or viewLifecycleOwner?
If observing from an Activity you can observe on onCreate() and use this for the LifecycleOwner as stated here:
If you have a lifecycle-aware component that is hooked up to the lifecycle of your activity it will receive the ON_CREATE event. The method annotated with #OnLifecycleEvent will be called so your lifecycle-aware component can perform any setup code it needs for the created state.
Now if you are observing within a Fragment you can observe on onViewCreated() or onActivityCreated() and you should use getViewLifecycleOwner() and here is why:
Get a LifecycleOwner that represents the Fragment's View lifecycle. In most cases, this mirrors the lifecycle of the Fragment itself, but in cases of detached Fragments, the lifecycle of the Fragment can be considerably longer than the lifecycle of the View itself.
As in the I/O talk Yigit says, the Fragment and its view has different lifecycles. You would need to identify if your LiveData is related to the fragment or its view and pass the one desired. The compiler will accept both since both are implementations of LifecycleOwner
It doesn't matter whether you do it on onViewCreated or onActivityCreated. Both are called when the fragment is inflated, onViewCreated first, onActivityCreated afterwards. It's really a matter of preference.
The LiveData object takes a LifecycleOwner, and both Fragment and Activity implement the interface, so you just need to pass this.
In my Activity I have a lateinit property called controller that my Fragment uses.
This property is initialized in Activity.onCreate(). My Fragment gets its reference back to my Activity through onAttach(). The Fragment then calls myActivity.controller in Fragment.onCreate().
Normally controller is first initialized in Activity.onCreate(), and after that, the Fragment is added. So this works just fine.
But when my Activity has been killed, it tries to recreate itself and its fragments. This causes Fragment.onCreate() to be called before the initialization took place in Activity.onCreate().
These are the options I see right now:
initialize controller before super.onCreate() (if that's even possible)
move the call to myActivity.controller to a later lifecycle callback, as onViewCreated()
something with ::controller.isInitialized available in Kotlin 1.2
What is my best option here?
By reviewing the Fragment lifecycle, in fact the safest point to do it will be #onActivityCreated(android.os.Bundle).
Even when #onAttach() looks like it is called when the Fragment is attached to the Activity, I'm not sure if this is completely guaranteed, as the old #onAttach(android.app.Activity) is deprecated, and the new #onAttach(android.content.Context) is recommended.
The best way to handle such scenario where an object is used before initialization is by checking with isInitialized() property and then using it.
I faced with that problem when I started to use ViewPager. As every page is nested Fragment, I can't call setRetainInstance(true) for it. So, I need to store Fragment's state to a Bundle and cancel/recall remote API methods onViewAttached/Detached which I don't want to.
What I learn about this situation:
I can use RecyclerViewPager to avoid using nested Fragments, but I still can't keep an instance of Presenter. One of the ways is to use a static field, but the same thing can I do with the Fragment.
Create some kind of rootViewPager under MainActivity and use it in Fragments via setVisibility(GONE/VISIBLE) and replacing Adapter. So, every Fragment placed into this ViewPager will not have parent Fragment and I will solve my case. Suitable and elegant, but not the best solution as I think.
Any other variants?
In Mosby 3.0 Presenters can be retained even without setRetainInstance(true) ... I would suggest to wait until 3.0 release ...
So, I need to store Fragment's state to a Bundle and cancel/recall
remote API methods onViewAttached/Detached which I don't want to.
Mosby 2.0 does exactly that for you but you have to make your ViewState and your data implement Parcelable. In that case the presenter instance won't survive screen orientation changes, but presenter will "resume" on the same state / point (a new presenter instance will be created, async tasks etc. might be restarted too). See RestorableViewState (javadoc is slightly outdated, because it mentions that this is the only way to work with activities which since Mosby 2.0 is not true anymore)
I find Fragment#setRetainInstance(true) confusing. Here is the Javadoc, extracted from the Android Developer API:
public void setRetainInstance (boolean retain)
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being re-created.
onAttach(Activity) and onActivityCreated(Bundle) will still be called.
Question: How do you as a developer use this, and why does it make things easier?
How do you as a developer use this
Call setRetainInstance(true). I typically do that in onCreateView() or onActivityCreated(), where I use it.
and why does it make things easier?
It tends to be simpler than onRetainNonConfigurationInstance() for handling the retention of data across configuration changes (e.g., rotating the device from portrait to landscape). Non-retained fragments are destroyed and recreated on the configuration change; retained fragments are not. Hence, any data held by those retained fragments is available to the post-configuration-change activity.
It's very helpful in keeping long running resources open such as sockets. Have a UI-less fragment that holds references to bluetooth sockets and you won't have to worry about reconnecting them when the user flips the phone.
It's also handy in keeping references to resources that take a long time to load like bitmaps or server data. Load it once, keep it in a retained fragment, and when the activity is reloaded it's still there and you don't have to rebuild it.
Added this answer very late, but I thought it would make things clearer. Say after me. When setRetainInstance is:
FALSE
Fragment gets re-created on config change. NEW INSTANCE is created.
ALL lifecycle methods are called on config change, including onCreate() and onDestroy().
TRUE
Fragment does not get re-created on config change. SAME INSTANCE is used.
All lifecycle methods are called on config change, APART FROM onCreate() and onDestroy().
Retaining an instance will not work when added to the backstack.
Don't forget that the above applies to DialogFragments as well as Fragments.
The setRetainInstance(boolean) method is deprecated, use ViewModels instead.
The setRetainInstance(boolean) method on Fragments has been deprecated as of Version 1.3.0 of fragment API.
With the introduction of ViewModels, developers have a specific API for retaining state that can be associated with Activities, Fragments, and Navigation graphs. This allows developers to use a normal, not retained Fragment and keep the specific state they want retained separate.
This ensures that developers have a much more understandable lifecycle for those Fragments (one that matches all of the rest of their Fragments) while maintaining the useful properties of a single creation and single destruction (in this case, the constructor of the ViewModel and the onCleared() callback from the ViewModel).