带有保存状态的视图模型、Jetpack导航、数据绑定和Coroutines

评论 24 浏览 0 2019-06-25

自推出以来,ViewModel已经成为最“核心”的Android Jetpack库之一。根据我们2019年的开发者基准数据,超过40%的安卓开发者已经将ViewModels添加到他们的应用程序中。如果你不熟悉ViewModels,可能不清楚为什么会出现这种情况:ViewModels通过将数据与你的UI分离来促进更好的架构,使其易于处理UI生命周期,同时也提高了测试能力。关于完整的解释,请查看ViewModels。一个简单的例子官方文档

由于ViewModels是如此的基础,在过去的几年中,有很多工作都是为了让它们更容易使用,更容易与其他库集成。在这篇文章中,我将介绍四种集成方式。

  1. ViewModels中的保存状态 —ViewModel的数据在背景进程重启后仍能存活。
  2. 带有ViewModel的NavGraph — ViewModels和导航库的整合
  3. 在数据绑定中使用ViewModels — 使用ViewModels和LiveData轻松实现数据绑定
  4. viewModelScope — Kotlin Coroutines和ViewModels的整合

ViewModels中的保存状态:ViewModel数据在背景进程重启后仍能存活。

lifecycle-viewmodel-savedstate:1.0.0-alpha01中添加,Java和Kotlin都是如此。

onSaveInstanceState的挑战

当ViewModels最初推出时,有一个涉及onSaveInstanceState的混乱问题。活动和片段可以通过三种方式被销毁。

1.你的意思是要永久地导航离开:用户导航离开或明确地关闭活动—比如按后退按钮或触发一些调用finish()的代码。该活动已永久消失。

2.配置发生了变化:用户旋转了设备或做了一些其他的配置变化。该活动需要立即重建。

3. 应用程序被置于后台并且其进程被终止:当设备内存不足并需要快速释放一些内存时会发生这种情况。当用户导航回您的应用程序,活动将需要重建。

在情况2和3中,你想重建活动。ViewModels总是帮助你处理情况2,因为ViewModel在配置改变时不会被销毁;但在情况3中,ViewModel也会被销毁,所以你实际上需要使用活动中的onSaveInstanceState回调来保存和恢复数据。我在ViewModels中更详细地讨论了这个棘手的区别。Persistence, onSaveInstanceState(), Restoring UI State and Loaders中详细介绍了这种棘手的区别。

保存的状态模块

ViewModel保存的状态模块帮助你处理第三种情况:进程死亡。ViewModel不再需要向活动发送和接收状态。相反,你现在可以在ViewModel中处理保存和恢复数据。现在ViewModel可以真正处理和持有它自己的所有数据。

这是用SavedStateHandle完成的,它与Bundle非常相似;它’是一个数据的键值映射。这个SavedStateHandle “bundle”在ViewModel中,它可以在后台进程死亡后继续存在。任何你以前必须保存在onSaveInstanceState中的数据现在都可以保存在SavedStateHandle中。例如,用户的id是你可能保存在SavedStateHandle中的东西。

设置 "保存状态 "模块

让我们看看如何使用这个新模块。请注意,下面显示的代码与这个代码非常相似,来自Lifecycles Codelab的第6步。那段代码是用Java编写的,下面的代码是用Kotlin编写的。

第1步:添加依赖关系

SavedStateHandle 目前处于 alpha 状态(这意味着 API 可能会发生变化,我们正在寻求反馈),并且它是一个独立的库。需要添加的依赖性是。

implementation ‘androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha01’

注意,如果您想了解库中发生的变化,请查看生命周期发布说明页面。

第2步:更新对ViewModelProvider的调用

接下来,你要创建一个具有SavedStateHandle的ViewModel类型。在你的活动或片段onCreate中,将你对ViewModelProvider的调用更新为。

// This ktx requires at least androidx.fragment:fragment-ktx:1.1.0 or 
// androidx.activity:activity-ktx:1.0.0
val viewModel by viewModels { SavedStateVMFactory(this) }
// Or the non-ktx way...
val viewModel = ViewModelProvider(this, SavedStateVMFactory(this))
            .get(MyViewModel::class.java)

创建ViewModel的类是ViewModel工厂,并且有一个ViewModel工厂可以制造具有SavedStateHandle的ViewModel,该工厂名为SavedStateVMFactory。现在创建的ViewModel将有一个SavedStateHandle,与传入的活动/片段相关。

注意。即将发布的Androidx activityfragment库的alpha版本将在7月推出。在这些版本中(如此处所述),当你在活动或片段中制作一个ViewModel时,SavedStateVMFactory将成为默认的ViewModelProvider.Factory。这意味着,如果你’正在使用Androidx活动片段的最新alpha版本,你将不需要添加lifecycle-viewmodel-savedstate依赖或明确使用SavedStateVMFactory。简而言之,当这种变化发生时,如果你’正在使用新的alpha版本,你可以跳过步骤1和2,直接进入下面的步骤3。

第3步:在ViewModel中使用SaveStateHandle。

一旦你完成了这些,你就可以在你的ViewModel中使用SavedStateHandle。下面是一个在SavedStateHandle中保留用户ID的例子。

class MyViewModel(state : SavedStateHandle) : ViewModel() {

    // Keep the key as a constant
    companion object {
        private val USER_KEY = "userId"
    }
    
    private val savedStateHandle = state
    
    fun saveCurrentUser(userId: String) {
        // Sets a new value for the object associated to the key.
        savedStateHandle.set(USER_KEY, userId)
    }
    
    fun getCurrentUser(): String {
        // Gets the current value of the user id from the saved state handle
        return savedStateHandle.get(USER_KEY)?: ""
    }
}

  1. Construct: MyViewModel将SavedStateHandle作为构造函数的参数。
  2. 保存: saveNewUser方法展示了一个在SavedStateHandle中保存数据的例子。你保存了USER_KEY的键值对,然后是当前的userId。当数据在ViewModel中更新时,它应该被保存在SavedStateHandle中。
  3. Retrieve: savedStateHandle.get(USER_KEY)是一个获取保存在SaveStateHandle中的当前值的例子。

现在,如果活动由于旋转而被破坏,或者由于操作系统杀死你的进程以释放内存,你可以确保SavedStateHandle会有你的数据。

通常情况下,你会在你的ViewModel中使用LiveData。为此,你可以使用SavedStateHandle.getLiveData()方法。这里’是一个用LiveData替换getCurrentUser的例子,它可以进行观察。

// getLiveData gets MutableLiveData associated with a key. 
// When the value associated with the key updates, the MutableLiveData does as well.
private val _userId : MutableLiveData<String> = savedStateHandle.getLiveData(USER_KEY)

// Only expose a immutable LiveData
val userId : LiveData<String> = _userId

要了解更多信息,请查看生命周期Codelab的第6步官方文档

ViewModel和Jetpack导航:带有ViewModel的NavGraph

navigation 2.1.0-alpha02中添加,Java和Kotlin都是如此。

ViewModel共享的挑战

Jetpack Navigation开箱即用,适用于设计有相对较少的活动&mdash;甚至只有一个&mdash;包含多个片段的应用程序。我们选择这种架构的一些原因在Ian Lake’出色的演讲Single Activity:为什么、何时和如何。其中一个原因是,这种架构允许你通过创建一个活动共享的ViewModel在不同的目的地之间共享数据。你使用活动创建一个ViewModel,然后你可以从活动的任何片段中获得对该ViewModel的引用。

// Any fragment's onCreate or onActivityCreated
// This ktx requires at least androidx.fragment:fragment-ktx:1.1.0
val sharedViewModel: ActivityViewModel by activityViewModels()

现在想象一下,我们有一个单一的活动应用程序,我们有八个片段目的地。其中,有四个是购物结账流程。

带有购物结账流程中的一些屏幕的导航图。

对于结账流程中的这四个目的地来说,共享数据是很重要的,比如发货地址或用户是否使用了优惠券代码。我们将把这些信息放在ViewModel中,但ViewModel与什么相关?这些信息对应用程序的其他部分并不重要,但以前我们对共享ViewModel的唯一选择是将ViewModel与活动相关联。这意味着所有的八个目的地都可以访问这个ViewModel。

ViewModel NavGraph集成

Navigation 2.1.0引入了与导航图关联的ViewModels。在实践中,这意味着你可以采取一系列相关的目的地,如入职流程、登录流程或结账流程;将它们放入一个嵌套的导航图;并在这些屏幕之间启用共享数据。

要创建一个嵌套的导航图,你可以选择你的屏幕,点击右键,然后选择移动到嵌套图→ 新图:

显示如何 "移动到嵌套图 "的屏幕截图

在XML视图中,注意嵌套的导航图的id,在这个例子中是checkout_graph

<navigation app:startDestination="@id/homeFragment" ...>
    <fragment android:id="@+id/homeFragment" .../>
    <fragment android:id="@+id/productListFragment" .../>
    <fragment android:id="@+id/productFragment" .../>
    <fragment android:id="@+id/bargainFragment" .../>
    
    <navigation 
    	android:id="@+id/checkout_graph" 
    	app:startDestination="@id/cartFragment">

        <fragment android:id="@+id/orderSummaryFragment".../>
        <fragment android:id="@+id/addressFragment" .../>
        <fragment android:id="@+id/paymentFragment" .../>
        <fragment android:id="@+id/cartFragment" .../>

    </navigation>
    
</navigation>

一旦你完成了这些,你就可以使用by navGraphViewModels来获得ViewModel。

val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph)

这在Java编程语言中也是可行的,使用。

public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    // Other fragment setup code
    
    NavController navController = NavHostFragment.findNavController(this);
    
    ViewModelProvider viewModelProvider = new ViewModelProvider(this,
        navController.getViewModelStore(R.id.checkout_graph));
    
    CheckoutViewModel viewModel = viewModelProvider.get(CheckoutViewModel.class);
    
    // Use Checkout ViewModel
}

请注意,嵌套图与导航图的其他部分是封装在一起的。你可以导航到一个嵌套图(你会去到嵌套图的起始目的地),但你不能从图的外部直接导航到嵌套图中的一个特定目的地。因此,它们是为封装的屏幕集合而设计的,比如结账流程或登录流程。

ViewModel NavGraph集成是I/O 2019上宣布的新导航功能之一。更多内容请查看讲座Jetpack Navigation文档

ViewModel和数据绑定:在数据绑定中使用你的ViewModel和LiveData。

Android Studio 3.1中添加了Java和Kotlin两种语言。

所有这些LiveData的繁文缛节

这个集成是一个老东西,但也是一个好东西。ViewModels通常包含LiveData,而LiveData是为了被观察。通常这意味着在片段中添加一个观察者。

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    
    myViewModel.name.observe(this, { newName ->
        // Update the UI. In this case, a TextView.
        nameTextView.text = newName
    })
  
}

数据绑定库是关于观察你的数据和更新UI。通过同时使用ViewModel、LiveData和Data Binding,您可以删除之前的LiveData观察代码,并直接从布局XML中引用您的ViewModel和LiveData。

使用数据绑定、ViewModel和LiveData

假设在你的 XML 布局中,你想引用你的 ViewModel。

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="viewmodel" 
                  type="com.android.MyViewModel"/>
    </data>
    <... Rest of your layout ...>
</layout>
  

要使用LiveData与数据绑定,你只需要调用binding.setLifecycleOwner(this),然后将你的ViewModel传递给你的绑定,就像这样。

class MainActivity : AppCompatActivity() {
    
    // This ktx requires at least androidx.activity:activity-ktx:1.0.0
    private val myViewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
      
         //Inflate view and create binding
        val binding: MainActivityBinding = 
            DataBindingUtil.setContentView(this, R.layout.main_activity)

        //Specify this activity as the lifecycleOwner for Data Binding
        binding.lifecycleOwner = this
        
        // Pass the ViewModel into the binding
        binding.viewmodel = myViewModel
    }

}

现在在你的布局中,你可以使用你的ViewModel。如下图所示,我将文本设置为viewmodel.name

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="viewmodel" 
                  type="com.android.MyViewModel"/>
    </data>
    <TextView
            android:id="@+id/name"
            android:text="@{viewmodel.name}"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"/>
</layout>

请注意,viewmodel.name可以是一个字符串或一个LiveData。如果它是一个LiveData,用户界面将在LiveData发生变化时更新。

ViewModel和Kotlin Coroutines : viewModelScope

在生命周期2.1.0中添加 仅限Kotlin。

安卓系统中的程序员

Kotlin Coroutines是处理异步代码的一种新方式。另一种处理异步代码的方式是使用回调。回调很好,但如果你正在编写复杂的异步代码,你可能最终会有很多层嵌套的回调;这使你的代码难以理解。Coroutines简化了所有这些,并且还提供了一种简单的方法来确保你不会阻塞主线程。如果你是coroutines的新手,有一篇名为Coroutines on Android的深度博文系列,以及codelab Using Kotlin Coroutines in your Android App

一个简单的coroutine看起来就像一个做一些工作的代码块。

// Don't use GlobalScope - for example purposes only
GlobalScope.launch {
    longRunningFunction()
    anotherLongRunningFunction()
}

在这里,我只启动了一个 coroutine,但很容易启动数百个 coroutine,并有可能失去对它们的跟踪;如果你失去了对一个 coroutine 的跟踪,并且它正在运行一些你打算停止的工作,这就是所谓的 工作泄露

为了避免工作泄露,你应该通过将它们添加到CoroutineScope来组织你的轮回,这是一个跟踪轮回的对象。CoroutineScope可以被取消;当你取消一个范围时,它们会取消所有相关的coroutines。上面我使用的是GlobalScope,顾名思义,它是一个全球可用的CoroutineScope。使用GlobalScope通常是好的做法,原因与编写全局可访问的变量通常不好一样。因此,你需要创建一个作用域,或者获取对它的访问。在ViewModels中,如果你使用viewModelScope,这很容易。

查看模型的范围

通常情况下,如果你的ViewModel被销毁了,那么与ViewModel相关的一堆 "工作 "也应该被停止。

例如,假设你准备在屏幕上显示一个位图。这是一个你应该在不阻塞主线程的情况下工作的例子,如果你永久地远离或关闭屏幕,这些工作应该被停止。对于这样的工作,你应该使用viewModelScope

viewModelScope是ViewModel类上的一个Kotlin扩展属性。它是一个CoroutineScope,一旦ViewModel被销毁(当onCleared()被调用时)就会被取消。因此,当你使用ViewModel时,你可以使用这个范围来启动所有的coroutine。

这里有一个小的例子。

class MyViewModel() : ViewModel() {

    fun initialize() {
        viewModelScope.launch {
            processBitmap()
        }
    }
        
    suspend fun processBitmap() = withContext(Dispatchers.Default) {
        // Do your long running work here
    }
    
}

如果你正在使用Kotlin Coroutines和ViewModels,优秀的博文Easy Coroutines in Android: viewModelScope会有更多细节。更多关于Coroutines和架构组件的信息,请查看文档和讲座Understand Kotlin Coroutines on Android

结语

综上所述,我们的目标是:"我们的目标"。

  1. ViewModels用SavedStateHandle模块来处理onSaveInstanceState的情况。
  2. 你可以将一个ViewModel的范围扩大到Jetpack Navigation NavGraph,以便在片段之间进行更精确和封装的数据共享。
  3. 如果你使用数据绑定库和ViewModels,你可以将你的ViewModel传递给你的绑定。如果你也使用LiveData,请使用binding.setLifecycleOwner(lifecycleOwner)
  4. ...如果你使用Kotlin Coroutines与ViewModel,那么当ViewModel被销毁时,使用viewModelScope来自动取消你的coroutines。

这些集成中有许多是来自社区的直接反馈和请求。如果你正在寻找ViewModel的某个功能或集成,你可以关注功能请求列表,并考虑提出你自己的请求

若要了解有关架构和 Android Jetpack 的最新进展,请关注Android 开发人员媒介博客,并密切关注AndroidX 发布说明

对这些功能中的任何一个有疑问?请留下评论!谢谢你的阅读!

特别感谢Ian Lake、Yigit Boyar、Jose Alcérreca、Sean McQuillan、Jisha Abubaker和Alex Michael Cook的修改和贡献。

最后更新2022-11-02
24 个评论
#1 Igor Ganapolsky 2019-09-09

SavedStateHandle是否会取代存储库?

#2 alexshr 2019-08-05

问题是关于实现双向绑定(从UI中更新LiveData的值)。

这很有说服力,而且真的很有效果。

<android.support.design.widget.TextInputEditText android:text=”@={viewModel.commentText}”/>

但我还想存储LiveData的状态!这有什么办法?有什么好的方法吗?

#3 4gus71n 2019-07-11

快速的问题;我的大部分LiveData属性都是暴露ViewModel状态的密封类,密封类不能实现Parcelable,那么有没有办法将密封类存储在SavedStateHandle上呢?

Gabor Varadi 2020-04-25

密封的类可以实现Parcelable。

#4 Stavro Xhardha 2019-06-26

只是一个关于SavedStateVMFactory的实际使用的问题,如果我有另一个工厂与所需的ViewModel的依赖关系,我如何在ViewModel的实例化中组合/使用它?比如说。

someViewModel = ViewModelProviders.of(this, someViewModelFactory)
    .get(SomeViewModel::class.java)

那么,如果我放弃了我的自定义ViewModelFactory,我应该如何将依赖关系传递给ViewModel呢?

#5 Ro Bert 2021-07-27

如何在Fragments之间使用SavedState共享一个ViewModel?

#6 Chuck Stein 2021-07-01

当在一个流程中的多个屏幕之间共享ViewModel时,最佳做法是什么?除了必须在屏幕之间共享的数据的共享 ViewModel 之外,每个屏幕是否应该有一个单独的 ViewModel,用于处理该屏幕特有的状态?

谢谢你!

#7 Joydeep Das 2021-02-02

有一个问题,在存储和性能方面,savedStateHandler是否与操作系统在onSaveInstanceState()中的处理方式相同,而是被转移到ViewModel上?

#8 Chethan Mandya 2020-07-26

SavedStateHandle,我们是否可以用它来进行片段通信,我可以设置/获取实时数据对象。使用它们会不会有什么隐患,我相信SaveStateHandle不是用来保存更大的对象的?如果我使用这个片段通信,你怎么说?

#9 Ivan Schuetz 2020-04-29

只有在实际需要时才序列化状态不是更好吗?这看起来像什么呢(也许是将片段的onSaveInstanceState转发到视图模型)?

#10 Adam Hurwitz 2020-02-25

这是一篇了解ViewModel组件基本原理的好文章我使用了最新的Lifecycle 2.2.0版本,以便为ViewModel组件添加自定义参数和Saved State。

见。Optimizing Android ViewModel with Lifecycle 2.2.0 🤖🧰

#11 ror 2020-02-16

很好的文章。请将工厂更新为AbstractSavedStateViewModelFactory(我想这只是新名字)。

#12 AndroidDeveloperLB 2020-01-27

似乎SavedStateVMFactory被替换成了SavedStateViewModelFactory,这里应该有更多的变化。你能给我指点一下这方面的最新文章/教程/例子吗?

#13 Igor Ganapolsky 2019-11-02

是否有一个代码样本/github repo来说明SavedStateHandle的用法?

#14 Igor Ganapolsky 2019-10-23

我们是否应该使用依赖性注入框架(即Koin)来注入ViewModels?

#15 Igor Ganapolsky 2019-09-10

是否有一个谷歌样本来说明 "SavedStateHandle "的用法?

#16 Igor Ganapolsky 2019-09-06

你能解释一下`by viewModels'的代码吗?我似乎不能让它编译,IDE抱怨类型推理的问题。

Joydeep Das 2021-02-02

你需要为片段加入KTX插件。

#17 Mitch Besser 2019-08-15

大家好......很确定这篇文章已经被作者抛弃了。

#18 Kai hello 2019-08-12

好的

#19 Mitch Besser 2019-07-17

我不明白你在这一节中写了些什么。

"ViewModels中的保存状态:ViewModel数据在背景进程重启后仍然存在"。

不使用保存的状态模块,活动(或片段)使用onSaveInstanceState(Bundle)来保存状态。onCreate(Bundle savedInstanceState)在Bundle不是null时恢复状态,因为null表示之前没有保存任何东西。

如何使用 "保存状态模块 "来实现?我看到Saved State Module允许使用一个键来保存和恢复数值,但这还不足以在进程死亡时保存和恢复数据。我还需要知道什么时候调用这些函数。是否有一个我应该覆盖的回调?

我是否应该在每次改变数值时保存我的状态?这似乎很浪费。每次我需要一个值的时候,我都要恢复吗?这似乎让存储库变得多余了。

你能否提供顺序,说明在流程死亡和配置变更中数据的流向?是否有一个使用存储库的例子?

为什么文档中说"......有一个特殊的方法:getLiveData(String key),它返回包裹在LiveData可观察到的值"。你能在这里定义一下 "特殊 "的含义吗?我什么时候使用get(key)与getLiveData(key)?

谢谢你。

Gabor Varadi 2020-04-25

它’都存储在一个Map<String, Object>中,并通过SavedStateRegistryOwnerFragment/Activity)所拥有的SavedStateRegistry自动持久化到onSaveInstanceState中。

#20 Raghunandan Kavi 2019-07-09

不错的文章。写得非常好。

#21 Kevin Herrera 2019-07-01

你好,我真的很难找到能清楚解释基本事物的好文档。我的核心问题是,当我试图将 "LifeCycles CodeLab "中的例子应用于我的Kotlin应用程序时,我遇到了我不理解的崩溃。

具体来说,我想在NavGraph集成中用Kotlin复制Timer的例子,但我发现我不太明白。

例如,当我从Fragment中调用ViewModelProviders方法时,我的应用程序会崩溃,除非我使用带有throw和Elvis操作符的 "run",为什么呢,有没有更好的方法?

接下来我不明白的是,为什么在Java中需要使用onChange方法,而在Kotlin中不仅不需要,而且似乎也不起作用(我擦掉了它,使里面的代码起作用);为什么,难道我做错了吗?

另外,我使用Init方法来替代ViewModel中来自Java的构造函数,而且每次我在片段之间改变时都会调用它,这正常吗? 最后,我没有看到任何关于在ViewModel上使用工厂的必要性的解释。我的意思是,当你需要使用一个非空的构造函数时,看起来你需要它们,但没有解释如何正确地调用它们,如果它们需要一直被使用*我并不总是向我的构造函数传递参数*,还有谁能指出一个好的教程,告诉像我这样的新手何时以及为什么使用 "by lazy "和 "by instance"?

Lyla Fujiwara 2019-07-12

嗨,凯文。

这里有很多问题,我将回答其中的几个问题

For example, My app crashes when I call the ViewModelProviders method from a Fragment, unless I use “run” with a throw and Elvis operators, why?, and is there a better way?

一个埃尔维斯运算符加上使用run,听起来像是你在试图处理无效性。在没有看到代码的情况下,我不确定在这种情况下什么是 null,所以很难提供建议。使用 Kotlin 中的 NavGraph 集成来获取 ViewModel 的那行代码不应要求进行 null 检查。

The next thing that I do not understand is why in Java I needed to use the onChange method, but in Kotlin it was not only not needed

这’是因为SAM 转换,这是 Kotlin 拥有的一项功能,它可以将“单一抽象方法”接口转换为lambda 表达式&mdash;简而言之,在 Kotlin 中,您可以使用 lambda 表达式而无需明确重写 onChanged

and also can somebody point to a good tutorial on when and why to use “by lazy” and “by instance” to people that are noobs like me?

上面的三个问题是很好的问题,但它们是一般的Kotlin问题,而不是专门针对ViewModels的。你可以通过搜索和查看各种堆栈溢出的答案来一点一点地学习东西;如果你想要更多的指导,我建议你查看一些Kotlin学习资源。如果你喜欢现场学习,我们也正在进行Kotlin/Everywhere活动,你附近可能会有一个

在这之后,我想看看Room with a View codelab in Kotlin,它将给你介绍ViewModel、LiveData和Room与Kotlin,或者我想参加Developing Android apps with Kotlin课程(时间投入更大,但更深入地了解做事背后的“原因” )。该课程假定你知道Kotlin,但真正深入到ViewModels、Navigation、架构等。我还建议看一下Sunflower示例应用程序

Also I used the Init method to substitute the constructor from Java in the ViewModel, and it seems to be called everytime I change between fragments, is this normal?

如果它被调用,那是因为ViewModel被重构了 - 如果它是一个全新的片段(而不是在配置更改后制作的片段),这一点是真的。导航组件在你导航时确实创建了新的片段,所以这是有道理的。除非你点击返回按钮,否则它不会把你 "带回 "一个片段。

I have not seen any explanation on the need to use factories on ViewModels. I mean, it looks like you need them when you need to use a non-empty constructor, but there is no explanation on how to correctly call them, if they need to be used all the time *I don’t pass arguments to my constructor always*

你是正确的&mdash;当你需要向ViewModel传递参数时,ViewModel工厂会被使用。当你需要向ViewModel中获取数据时,你有两个选择:要么使用工厂并通过构造函数传递数据,要么在ViewModel中创建一个setter方法并通过该方法设置数据。正如在另一条评论中提到的,在这个视频代码差异用Kotlin开发Android应用中,可以找到一个关于如何添加ViewModel工厂的更全面的例子。

#22 sbo 3000 2019-06-27

嘿!是否有可能使用DI的Saved State功能,或者在这种情况下我们被ViewModelProvider束缚住了?

Lyla Fujiwara 2019-07-12

你可以使用DI的Saved State功能。一般来说,要向ViewModel添加构造参数,你要做一个ViewModelProvider.Factory,然后在初始化ViewModel时传入工厂。因此,不使用Saved State模块的例子看起来像这样。

val viewModel by viewModels { MyViewModelFactory(repository) }
// Or
val viewModel = ViewModelProvider(this, MyViewModelFactory(repository)).get(MyViewModel::class.java)

关于如何添加ViewModel工厂的更全面的例子可以在这个视频代码差异中找到,这些代码来自我帮助制作的用Kotlin开发Android应用程序课程。

对于Saved State模块,你不需要从一个普通的工厂扩展,而是从AbstractSavedStateVMFactory扩展。

val viewmodel by viewModels { MySavedStateViewModelFactory(repository, this) }
// Or
val viewModel = ViewModelProvider(this, MySavedStateViewModelFactory(repository, this)).get(MyViewModel::class.java)

如果你’具体谈论的是使用Dagger、Koin或其他框架,则需要更多的代码来连接一切。我绝非Dagger专家,但询问同事是否还有其他事情要做,他们建议你可能需要使用模块中的SavedStateRegistryOwner

@Binds
internal abstract fun bindSavedStateRegistryOwner(myActivity: MyActivity): SavedStateRegistryOwner

对于Dagger + ViewModels,我建议查看GithubBrowser架构组件样本和2018年IO Sched样本

#23 Lou Morda 2019-06-27

非常感谢您提供的这个:viewModelScope.sponse

太多的例子还在使用GlobalScope!谢谢你 Lyla Fujiwara

#24 seyfullah bilgin 2019-06-26

感谢您的精彩文章。我想问一个问题。

目前,我的viewmodels采取Repository参数,提供本地和远程的数据。在这种情况下,当我们考虑SavedStateHandle和Repository参数时,viewmodels必须按照这种方法接受两个参数。这种方法听起来并不符合清洁代码的原则。你能给我一些建议来解决这个问题吗?

Lyla Fujiwara 2019-07-12

嘿,Seyfullah — 谢谢你的问题。所以我想注意的第一件事是,从活动1.1.0-alpha01片段1.2.0-alpha01发布起,SavedStateVMFactory是活动和片段的默认工厂。这意味着你将不需要手动传入SavedStateHandle,它将直接为你提供。

至于自己解决这个问题,你能说明你’寻求什么便利?例如,如果问题是模板,我打赌你可以为你的片段和活动写一个扩展函数,当需要ViewModel时,总是使用SavedStateVMFactory &mdash;但我不确定这是否解决了你的根本问题。