viewmodel_viewmodel保存状态审查

viewmodel

The android framework has a curious case of killing any app that’s in the background for more than a few minutes.

android框架有一个奇怪的案例,那就是杀死所有后台运行的应用超过几分钟。

Take this for example:

以这个为例:

class RedditViewModel(
    private val redditRepository: RedditRepository
) : ViewModel() {
    val redditPostData = MutableLiveData<RedditPost>()
    
    fun getRedditPost() {
        viewModelScope.launch {
            redditPostData.value = RedditPost.Loading
            val post: RedditPost.Post = redditRepository.getPost()
            redditPostData.value = post
        }
    }
}
sealed class RedditPost {
    object Loading : RedditPost()
    data class Post(val text: String) : RedditPost()
}

The RedditViewModel is responsible for fetching a post from Reddit while the calling activity/fragment is responsible for observing the redditPostData variable.

RedditViewModel负责从Reddit获取帖子,而调用活动/片段负责观察redditPostData变量。

Once the post is fetched the activity’s observer is notified and we show it to the user.

提取帖子后,系统会通知活动的观察者,然后我们将其显示给用户。

Image for post
award winning design™
屡获殊荣的设计™

This is all well and good until you put your app in the background and wait for a few minutes.

一切都很好,直到您将应用程序置于后台并等待几分钟。

Image for post
Or just kill the app yourself by clicking this little icon in the LogCat
或者只是通过单击 LogCat中的这个小图标自己杀死该应用 程序

Bringing the app to the foreground you might notice the post is all gone and we’re back to the initial CLICK ME state.

将应用程序置于前台,您可能会注意到帖子已全部消失,我们回到了初始“ 点击我”状态。

It seems that process-death murdered the redditPostData along with a bunch of other stuff.

似乎进程死亡杀死了redditPostData以及其他一些东西。

In the olden days before android MVVM the UI state and other important variables would be kept with onSaveInstanceState() . Since viewmodels made an entrance, that has been sort of replaced in favor of using livedata.

在android MVVM之前的旧时代,UI状态和其他重要变量将通过onSaveInstanceState( )保留。 自从视图模型进入市场以来,为了使用livedata ,已将其替换。

The issue still exists though and is the sneaky cause of many bugs and crashes. ViewModel-SavedState is stable as of the end of January and is Google’s attempt at addressing the “just restart the app lol 🤣” crowd.

但是,该问题仍然存在,并且是许多错误和崩溃的狡猾原因。 截至1月底, ViewModel-SavedState稳定,这是Google试图解决“只是重新启动应用程序而已”人群的尝试。

霍尔 (Hol’ up)

Add some dependencies first:

首先添加一些依赖项:

implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0"
 implementation "androidx.fragment:fragment-ktx:1.1.0"
 implementation "androidx.lifecycle:lifecycle-extensions:2.2.0-alpha02"
 implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha02"

Source code for the project can be found here.

该项目的源代码可以在这里找到。

匕首和其他灾难 (Dagger and other disasters)

The RedditViewModel needs a SaveStateHandle to do the job of saving stuff on process death.

该RedditViewModel需要SaveStateHandle做的过程中死亡节约的东西的工作。

class RedditViewModel(
    private val redditRepository: RedditRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel()

When working with Dagger one would normally use a ViewModelProvider.Factory that will be responsible for instantiating the ViewModel.

使用Dagger时,通常会使用ViewModelProvider.Factory负责实例化ViewModel。

Since a SaveStateHandle is needed here, we’ll use AbstractSavedStateViewModelFactory instead.

由于这里需要一个SaveStateHandle ,因此我们将使用AbstractSavedStateViewModelFactory

class RedditViewModelFactory @Inject constructor(
    private val redditRepository: RedditRepository,
    activity: Activity
) : AbstractSavedStateViewModelFactory(activity as SavedStateRegistryOwner, null) {


    override fun <T : ViewModel?> create(
        key: String, 
        modelClass: Class<T>,
        handle: SavedStateHandle): T {
        @Suppress("UNCHECKED_CAST")


        return RedditViewModel(redditRepository, handle) as T
    }
}

RedditRepository is our own little class that can be instantiated in a dagger module or using the @inject annotation.

RedditRepository是我们自己的小类,可以在dagger模块中或使用@inject注释实例化。

interface RedditComponent {
    fun inject(redditActivity: RedditActivity)


    @Component.Builder
    interface Builder {
        @BindsInstance
        fun activity(activity: Activity): Builder


        fun build(): RedditComponent
        fun appComponent(appComponent: AppComponent): Builder
    }
}

Providing the activity is not too hard either it seems. 🤔

看起来,提供活动并不难。 🤔

I guess now is the right time to tie these together.

我想现在是将这些联系在一起的正确时机。

Image for post

Reddit驱动的开发 (Reddit driven development)

The activity should be all ready to get a RedditViewModel after we inject the factory in it like we normally do.

在像往常一样注入工厂之后,该活动应该已经准备就绪,可以获取RedditViewModel

class RedditActivity : AppCompatActivity() {
    @Inject
    internal lateinit var redditViewModelFactory: RedditViewModelFactory
    private val redditViewModel: RedditViewModel by viewModels { redditViewModelFactory }
    lateinit var redditComponent: RedditComponent


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        injectDependencies()
        setContentView(R.layout.activity_reddit)


        redditViewModel.redditPostData.observe(this) { state ->
            when (state) {
                RedditPost.Loading -> button.text = "Loading"
                is RedditPost.Post -> button.text = state.text
            }
        }


        button.setOnClickListener {
            redditViewModel.getRedditPost()
        }
    }
}

Injecting stuff in an activity is done with this classic that probably has a million different variations I’m missing.

可以通过这种经典的方式在活动中添加内容,而我可能会错过一百万种不同的变体。

private fun injectDependencies() {
        if (!::redditComponent.isInitialized) {
            redditComponent = DaggerRedditComponent.builder()
                .activity(this@RedditActivity)
                .appComponent(RedditApplication.component)
                .build()
        }
        redditComponent.inject(this)
    }

While this isn’t really a Dagger tutorial (as if anyone can fully understand Dagger), you would need an application component and custom scopes to make this work. Check the repo for the whole thing.

尽管这实际上不是Dagger教程(好像任何人都可以完全理解Dagger一样),但您需要一个应用程序组件和自定义范围才能使此工作正常进行。 检查回购了整个事情。

Or get your DI set up the way you like it.

或以您喜欢的方式设置DI

那ViewModel呢 (What about the ViewModel)

The RedditViewModel has changed slightly:

RedditViewModel进行了少许更改:

class RedditViewModel(
    private val redditRepository: RedditRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    val redditPostData = MutableLiveData<RedditPost>()
    init {
        savedStateHandle.get<RedditPost>(KEY_REDDIT)?.let { state ->
            redditPostData.value = state
        }
    }
    fun getRedditPost() {
        viewModelScope.launch {
            redditPostData.value = RedditPost.Loading
            val post: RedditPost.Post = redditRepository.getPost()
            post.run {
                redditPostData.value = this
                savedStateHandle.set(KEY_REDDIT, this)
            }
        }
    }
    companion object {
        private const val KEY_REDDIT = "key_reddit"
    }
}
  • SavedStateHandle is just a map with all the stuff we might save in there.

    SavedStateHandle只是一张地图,其中包含我们可能会保存在其中的所有内容

  • On init we check if there’s anything saved in the SavedStateHandle. If we find something then process death has probably happened. Let’s update the redditPostData with that value and go back to the state we were.

    初始化时,我们检查SavedStateHandle中是否保存了任何内容。 如果我们发现了什么,那么过程死亡可能已经发生 让我们用该值更新redditPostData ,然后回到原来的状态

  • Once the post is fetched we store that value in the SavedStateHandle and update the livedata like normal.

    提取帖子后,我们将该值存储在SavedStateHandle中,并像普通方法一样更新实时数据

When running the app this guy will pop up sooner or later:

运行该应用程序时,此人迟早会弹出:

Image for post

As with the activity’s onSaveInstanceState() , anything you would want to put in a bundle must implement Parcelable.

与活动的onSaveInstanceState( ) ,要放入捆绑包中的任何东西都必须实现Parcelable

Let’s try that then.

让我们尝试一下。

sealed class RedditPost {
    @Parcelize
    object Loading : RedditPost(), Parcelable
    @Parcelize
    data class Post(val text: String) : RedditPost(), Parcelable
}

Give the app another run and everything should work. Try initiating process death after fetching the reddit post with the red button found in LogCat too. The UI state should not be lost upon bringing the app in the foreground.

再次运行该应用程序,一切正常。 尝试使用LogCat中的红色按钮获取reddit帖子后,尝试启动进程终止 。 将应用程序置于前台时,UI状态不应丢失。

Image for post

小伙子 (Wew lad)

You might have noticed that the object stored in the bundle is a fairly simple one. Anything stored inside SavedStateHandle should be simple and lightweight.

您可能已经注意到,捆绑软件中存储的对象非常简单。 存储在SavedStateHandle中的所有内容都应该简单轻巧。

Big lists / complex objects that are important for the functionality of an app regardless of system initiated process-death should preferably be stored in a database (like Room for example!).

对于应用程序的功能而言至关重要的大列表/复杂对象,无论系统启动的进程终止如何,都应优先存储在数据库中(例如, Room !)。

Or you can just restart the app I guess. 🤡

或者,您也可以重新启动该应用程序。 🤡

Later.

后来。

翻译自: https://medium.com/swlh/viewmodel-saved-state-review-a532b780a9a2

viewmodel

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值