内容简介:Wanna keep in touch and be notified of similar posts?In MVVM-like view architectures, view state isn't enough to communicate from the ViewModel to the View. They have to be supplemented by some kind of events, which come with several challenges you should
Fragments have… Complicated lifecycles, to say the least. Let’s take a look at these, and how they all fit into the world of Jetpack today.
The instance lifecycle
We’ll start from the middle, with the lifecycle of the Fragment
class instance itself. I’ll, creatively, refer to this as the instance lifecycle .
This lifecycle technically begins when the Fragment
instance is created, usually via a constructor call or a conventional newInstance
method call. It may also be created by the Android system automatically in the process of restoring an application’s state. The instance lifecycle ends with the object being garbage collected after not being referenced anymore.
More practically speaking though, there are two pairs of lifecycle methods within this lifecycle that you’ll be using in your code. The onAttach
and onDetach
methods tell you when the Fragment is attached to a Context
via FragmentManager
, while the onCreate
and onDestroy
methods are where you’re generally supposed to initialize your Fragment and free up any resources used by it, respectively.
Since this is the lifecycle of the Fragment object in memory, any data you store in properties of the Fragment will only remain there for the scope of this lifecycle. There are multiple events which might cause your Fragment instance to be recreated, and lose such values, which is why the savedInstanceState
mechanism offers state saving and restoration, via the onSaveInstanceState
and onCreate
methods.
Most famously, a Fragment instance is torn down and a new one is created when the application goes through a configuration change* - same as with Activities.
We often simplify configuration changes to orientation changes , i.e. rotating the screen, as that’s what developers tend to encounter first (and the most) when learning Android development. However, there are other events which also trigger the same kind of configuration change and consequent instance recreation, such as screen size or language changes, or dark mode being toggled.
Another event that will end this lifecycle is process death, when your application is removed from memory entirely due to memory constraints of the device.
*Barring the usage of the deprecated setRetainInstance
method
The view lifecycle
Zooming in on the Fragment lifecycle described above, we’ll find a shorter lifecycle nested within the instance lifecycle. This is the view lifecycle , the lifecycle of the View
instance that the Fragment displays UI on.
Within the broader instance lifecycle, a Fragment might have multiple views created and torn down, over and over again.
For example, when you navigate away from a given Fragment, but it’s still in the backstack, its layout will be discarded, while the Fragment instance itself is kept in memory. When it needs to be displayed again (e.g. you navigate back to it), it will be prompted to inflate a new layout, and populate it with data again.
The view lifecycle starts with the onCreateView
and onViewCreated
calls (for inflation and initialization, respectively), and ends with onDestroyView
.
Since the Fragment might live longer than its view, any references to View
instances must be cleared in onDestroyView
, as the layout that those View
s are part of is no longer valid for the Fragment.
The logical lifecycle
Finally, zooming out from the lifecycles we’ve covered so far, we find the broadest lifecycle of a Fragment. I’ll refer to this as the logical lifecycle for lack of a better term. This lifecycle lasts the entire logical lifetime of the screen. It starts when the Fragment is first created, includes any configuration changes it goes through, and ends when the Fragment is destroyed for good, with no further recreations of it.
This is the lifecycle that the Jetpack ViewModel
class lives in, and it’s meant to be the solution to the problem of Fragment recreation clearing data and cancelling operations started in the Fragment. When the Fragment is recreated, the ViewModel
instance associated with it remains the same one as for the previous Fragment instance, which means that data placed in a ViewModel
will survive these configuration changes, as will operations that are started within the ViewModel
’s scope.
In terms of methods in the code, the logical lifecycle starts when the first instance of the Fragment and the corresponding ViewModel
instance is created (initialization can be placed in the ViewModel constructor), and ends with the onCleared
method of the ViewModel, which is called just slightly before the onDestroy
and onDetach
calls of the very last Fragment instance.
Note that while this lifecycle will survive configuration changes, ViewModel
s are still stored in memory and are not persisted on disk, therefore they (and this lifecycle) will still not last through process death. (Perhaps a fourth lifecycle should also be named, which includes process restarts in addition to recreations within a process ).
If you wish to persist data at the ViewModel level of your application, you can use the SavedStateHandle
mechanism. I’ve written about this in An Early Look at ViewModel SavedState and its follow-up, A Deep Dive into Extensible State Saving .
Now that we’ve covered the lifecycles that belong to a Fragment, let’s see how we encounter them in practice, when using some of the latest Jetpack constructs.
LifecycleOwners and observing LiveData
Jetpack introduced the concept of a LifecycleOwner
- a thing with a lifecycle. These can be used in various ways, one of the prominent ones being observing LiveData
.
Initially, the support Fragment
class itself was a LifecycleOwner
, which owned the instance lifecycle of a Fragment (from onCreate
to onDestroy
). This is what you could use to set up observations, which meant passing this
as a parameter to the observe
method:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel.state.observe(this, Observer { ... }) }
The LiveData
class has the ability of cleaning up registered observers itself when the associated lifecycle ends, which meant that registering an observer in onCreate
with the Fragment itself as the lifecycle owner would have it automatically removed in onDestroy
. Neat!
However, a problem arose when the values from a LiveData
like this were used to update UI. Observers of a LiveData
get the initial state when they start observing, and then they’re notified of changes when those happen. Having an observer set up in the instance lifecycle meant that when the view of the Fragment was recreated, the newly inflated layout wouldn’t be populated until the value of the LiveData
changed, as otherwise there wasn’t a reason to call the connected observer again. In theory, it has already seen the latest value.
The solution? Moving the observer to the onViewCreated
method, so that a new one is set up for each view of the Fragment that’s created. The initial notification of the observer would make sure that the new UI is filled with data.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.state.observe(this, Observer { ... }) }
However, if you did this while still using the Fragment as the LifecycleOwner
, you’ve introduced a bug in the code. You are adding new observers at the start of the view lifecycle, which may run its course multiple times, but they’ll only be removed at the end of the instance lifecycle. They’ll be registered from onViewCreated
until onDestroy
, and as the Fragment receives new views, they’ll begin stacking up.
For example, when your Fragment is on its second view lifecycle after the view was destroyed and then inflated again, every value placed in the LiveData
would trigger two observers, executing the same UI update code twice.
Oddly, this was an ongoing problem for a bit after the initial release of the architecture components. You can find discussions and workarounds from back in 2017 in this GitHub issue .
One fairly popular one was a ViewLifecycleFragment
implementation by Christophe Beyls, which extended Fragment
, and added a new lifecycle within it, which starts in onViewCreated
and ends in onDestroyView
.
If you wanted to observe a LiveData
correctly, you’d extend this class instead of Fragment
, and use viewLifecycleOwner
to set up the observer:
class ProfileFragment : ViewLifecycleFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.state.observe(viewLifecycleOwner, Observer { ... }) } }
The architecture components team eventually came around to fixing this shortcoming of the Fragment
class, by introducing their own viewLifecycleOwner
property with the same behaviour in the AndroidX Fragment
class, eliminating the need for the workaround.
class ProfileFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.state.observe(viewLifecycleOwner, Observer { ... }) } }
TL;DR: when observing LiveData
to update the state of the UI, do it in onViewCreated
, and use viewLifecycleOwner
to set up the observer.
Coroutine integrations
Let’s talk coroutines now. If you’re using a ViewModel
, then most of your background work such as fetching data from the network or disk should happen in the logical lifecycle . This makes sure that these data fetches continue through configuration changes, as it’s likely that you don’t want to cancel your requests and launch new ones when those happen (which you’d have to do if you performed these calls in the Fragment, in the instance lifecycle ).
This means that you should use coroutines that are scoped to your ViewModel
. An easy way to do this is by using the Jetpack viewModelScope
extension:
class ProfileViewModel: ViewModel() { fun loadProfile() { viewModelScope.launch { // Suspending calls to fetch data } } }
This scope is cancelled automatically when the onCleared
method is called. If you inject or create your own scope in a ViewModel
instead, remember to cancel it in the onCleared
method manually.
Another exciting coroutine integration in Jetpack is lifecycleScope
. This is an extension property on LifecycleOwner
, and it will give you a scope that’s cancelled when the given owner’s lifecycle ends.
To start coroutines in the instance lifecycle of a Fragment, you can use lifecycleScope
(which corresponds to this.lifecycleScope
). Coroutines started in this scope will be cleaned up in onDestroy
:
class ProfileFragment: Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { // Suspending stuff } } }
However, if you’re running coroutines that are UI related, such as using them for animations or collecting from a Flow for state updates similarly to observing LiveData
, you’ll likely want to do this in the view lifecycle , for the reasons described in the previous section: starting them in the instance lifecycle would make them too long-lived, past the view they belong to.
To do this, you’d have to use viewLifecycleOwner.lifecycleScope
when starting the coroutines. Since these will be cleared in onDestroyView
, you should set them up in the corresponding method at the start of the lifecycle, which is onViewCreated
:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { viewModel.stateFlow.collect { ... } } }
What’s next?
It’s clear that Fragment lifecycles are complex, and you have to keep the differences between them in mind whenever working with these APIs.
It’s been eluded to at last year’s Android Dev Summit that the instance and view lifecycles might end up being consolidated, so that only the view lifecycle remains, and when the view of a Fragment is torn down, the instance itself is destroyed with it. This is still yet to arrive, and would obviously be a breaking change in the Fragment APIs. Is it time for Fragment2
?
If you want to learn more about using Jetpack things correctly, take a look at my code review of a Pokedex project which touches on a variety of Jetpack APIs.
Finally, some shoutouts. I wrote a bit about these topics before, but the idea for this proper article was first sparked by conversations with RayWenderlich.com team members ( Evana Margáin and Nishant Srivastava ), and then this tweet from Vasya Drobushkov made me actually sit down and write it all up.
Wanna keep in touch and be notified of similar posts? Follow me @zsmb13 on Twitter !
Continue reading...
Thoughts about Event Handling on Android
In MVVM-like view architectures, view state isn't enough to communicate from the ViewModel to the View. They have to be supplemented by some kind of events, which come with several challenges you should be aware of.
In what may be the start of a new series, I code review a project that was posted on reddit recently and got very popular very quickly. Let's see what we can learn from it?
Thoughts about State Handling on Android
Handling the state of UI correctly is one of the prominent challenges of Android apps. Here are my subjective thoughts about some different approaches, which ones I tend to choose, and why.
Designing and Working with Single View States on Android
Describing the state of a screen is a common practice these days thanks to MVI popularizing the concept. Let's take a look at some examples of how you can design your state objects neatly using data classes and sealed classes, and how you can put them into practice.
以上所述就是小编给大家介绍的《Fragment Lifecycles in the Age of Jetpack》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Google 广告高阶优化(第3版)
【美】Brad Geddes(布兰德.盖兹) / 宫鑫、康宁、王娜 / 电子工业出版社 / 2015-9 / 99.00元
《Google 广告高阶优化(第3版)》可以说是Google AdWords的终极指南,内容非常丰富,第三版在内容上进行了全面更新。介绍了AdWords的最新最完整的功能,阐释其工作原理,也提供了相应的优化方法、策略和实践教程,读者可以随时在自己的PPC广告系列中进行实践。第三版增添了50多页新内容,涵盖Google系统最近的所有变动,包括广告系列结构的变化、出价调整器、重定向、视频广告功能、全新......一起来看看 《Google 广告高阶优化(第3版)》 这本书的介绍吧!