Fragment Lifecycles in the Age of Jetpack

栏目: IT技术 · 发布时间: 4年前

内容简介: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.

Fragment Lifecycles in the Age of Jetpack

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.

Fragment Lifecycles in the Age of Jetpack

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 .

Fragment Lifecycles in the Age of Jetpack

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.

Fragment Lifecycles in the Age of Jetpack

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.

Fragment Lifecycles in the Age of Jetpack

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.

Fragment Lifecycles in the Age of Jetpack

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 { ... })
    }
}

Fragment Lifecycles in the Age of Jetpack

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.

Let's Review: Pokedex

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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

精通Java并发编程(第2版)

精通Java并发编程(第2版)

[西] 哈维尔·费尔南德斯·冈萨雷斯 / 唐富年 / 人民邮电出版社 / 2018-10 / 89.00元

Java 提供了一套非常强大的并发API,可以轻松实现任何类型的并发应用程序。本书讲述Java 并发API 最重要的元素,包括执行器框架、Phaser 类、Fork/Join 框架、流API、并发数据结构、同步机制,并展示如何在实际开发中使用它们。此外,本书还介绍了设计并发应用程序的方法论、设计模式、实现良好并发应用程序的提示和技巧、测试并发应用程序的工具和方法,以及如何使用面向Java 虚拟机的......一起来看看 《精通Java并发编程(第2版)》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具