天天看点

在 Android 中创建视图模型 — Android 架构组件和 Kotlin

我最近才开始使用 Google 的 Android 架构组件,主要是ViewModel和LiveData,特别是因为现在您可以将LiveData对象用作数据绑定表达式中的可观察字段。使用ViewModel构建应用程序时,只要数据的范围处于活动状态(它可以是 Fragment 或 Activity),就会保留数据,从而避免不必要的对象重新创建和数据重新获取。未来最吃香的十大行业,这在配置更改(例如旋转)等情况下确实有帮助。有很多非常好的文章更好地解释了 ViewModel 和 LiveData 是什么,如何以及为什么使用它们。我建议先阅读它们。本文重点介绍如何创建 ViewModel 的实例,该库提供的方法的一些缺陷以及如何改进它们。

如前所述,ViewModel 的作用域限定为 Activity 或 Fragment,并且只要作用域仍然存在就必须存在。因此,我们不能在 期间实例化它onCreate,例如,因为它可能在同一个活动中被多次调用,并且会导致 ViewModel 不必要的重新创建,丢失其数据和状态。这个问题由带有 的架构组件解决ViewModelProviders,它负责使 ViewModel 保持活动状态并与范围配对:

这样,只有在同一个作用域中还不存在 ViewModel 时,它才会被实例化。如果它已经存在,库将返回它已经使用的相同实例。因此,即使您不使用lazy委托,并在 中进行此调用onCreate,您也可以保证每次收到相同的 ViewModel。它只会被创建一次。这很棒!

那么问题是什么?由于库负责创建 ViewModel,我们无法调用其构造函数,库在内部执行此操作,并且默认情况下它将始终调用空构造函数,从而无法将数据传递给它。更糟糕的是,如果 ViewModel 没有空的构造函数,则会导致致命的运行时异常。

例如,人们可能会尝试通过定义一个公共方法并在创建 ViewModel 后立即调用它来初始化 ViewModel onCreate,例如,这是错误的,因为这可能会在 Activity 或 Fragment 的生命周期中被多次调用。这将导致奇怪的行为,因为它可能导致已经准备好并正在使用的 ViewModel 上的数据重新获取和状态更改。

正确的做法是为 ViewModel 创建工厂,并将其传递给 ViewModelProviders。我们基本上是在教库如何创建我们想要的 ViewModel,所以它会在需要时使用工厂。这是工厂:

这就是我们将它传递给 ViewModelProviders 的方式:

在这个例子中,我将一个用户 ID 发送到 ViewModel,从意图的包中获取它。

这是我为此示例创建的 ViewModel:

在此示例中,我并未真正使用 ID,只是将值设置为名称 LiveData 以测试绑定。但代码放在这个 init 当库创建 ViewModel 并将其与 Activity 或 Fragment 配对时,块保证只执行一次。

图书馆提供的这个解决方案确实有效,但它有几个问题:

大量样板代码。大多数情况下,我们每个 Fragment 或 Activity 都有一个 ViewModel,我们最终不会在其他地方使用它。必须为每个 ViewModel 编写一个工厂真的很麻烦。

它不是类型安全的,UserViewModelFactory很可能会返回一个不同的 ViewModel,项目仍会编译,但应用程序会在运行时崩溃,因为我们得到的类型与ViewModelProviders.

我们可以通过创建一个 BaseViewModelFactory 来避免为每个不同的 ViewModel 创建一个工厂,它通过 lambda 从外部接收创建逻辑,并在必要时调用它,这已经减少了很多样板代码:

这就是我们如何使用它:

创建逻辑现在是传递给 BaseViewModelFactory 并将由它执行的代码块。

我们仍然不是类型安全的,我仍然觉得我们可以进一步减少我们需要编写的代码。因此,使用一些很酷的 Kotlin 功能,我们可以进一步改进这一点。不幸的是,这段代码不能从 Java 调用inline,reified还有 Kotlin 特定的功能用于克服 Java 的类型擦除引入的问题。这些扩展函数不应该在任何类中,它们可以在任何 Kotlin 文件中:

https://proandroiddev.com/media/fd3b0528f5426ec18fd82c459282ee3f

我们正在为 Fragment 和 Activity 编写扩展函数,因为它们是 ViewModel 的不同可能范围。

getViewModel()如果我们想使用默认的空构造函数,或者getViewModel{}在内部传递创建逻辑,如果我们想使用不同的构造函数,或者无论如何自定义创建,我们都可以使用我们的 ViewModel 。我们之前的例子现在看起来像这样:

我们进一步减少了冗长并使其类型安全!如果 中的逻辑getViewModel{}返回的类型与之前在 中声明的类型不同val,则项目将无法编译。比获得运行时错误要好得多。它也不那么冗余,因为我们尽可能地推断类型,并且不再需要传递::class.java类型。

我们可以getViewModel通过几种不同的方式调用:

如果val没有显式声明特定类型,并且在创建过程中没有使用自定义逻辑(未显式调用构造函数并返回对象),我们需要将类型传递给方法。

创建的活动的完整代码及其 XML 布局(使用数据绑定):

ViewModel 的完整代码已经在上面发布了。

奖金

如果在 Fragment 内调用,getViewModel将返回一个作用域为该 Fragment 的 ViewModel。我们可以通过调用将其范围限定为 Fragment 附加到的活动activity?.getViewModel()。这真的很强大,因为与之前的规则相同:ViewModel 将作用于 Activity,并且只要它存在,就会存在,并且只有一个实例。这是在附加到同一活动的片段之间共享数据的一种超级有效的方法,并且如果使用数据绑定,则允许它们对更改做出超级有效的反应。不再需要在活动中实现接口并在片段内保存对它们的引用来更改共享数据。