【译】使用Kotlin从零开始写一个现代Android 项目-Part2

本文介绍了如何使用Kotlin、MVVM架构、Repository模式和Android Manager来构建现代Android项目。重点讲解了MVVM模式的概念,强调了ViewModel作为连接View和Model的中介角色。通过代码示例展示了如何实现一个简单的MVVM模式,并讨论了使用LiveData处理配置变更的优势。此外,还提及了Repository模式的重要性,以及如何在Repository中处理远程和本地数据源。最后提到了Android Manager包装器用于检查网络连接状态,并预告了后续文章将涉及的内容。
摘要由CSDN通过智能技术生成

接着上一篇文章:使用Kotlin从零开始写一个现代Android 项目-Part1

5. MVVM架构+Repository模式+Android Manager

5.1 关于Android中的架构

长期以来,Android开发的项目中很少有架构,但是在过去几年,架构在各大Android社区广泛宣传。Activity即一切的时代过去了,Google发布了一个仓库叫做Android Architecture Blueprints,它包含了许多示例和不同架构的说明。最后,在Google IO/17大会上,发布了Android Architecture Components系列架构组件,可以帮助我们写更简洁、高质量的应用程序。你可以使用一个全部组件或者其中一个来构建你的应用程序,不过,我发现它们都非常有用,因此,本文剩下的部分和后面2部分中,我将介绍如何使用这些组件。首先,我将写一些有问题的代码,然后使用这些组件来重构,以看看这些库能帮我们解决什么问题。

这里主要有两种架构模式

  • MVP
  • MVVM

很难说它两谁更好,你应该都试试以后再决定。我个人更喜欢带有生命周期组件的MVVM架构,本系列将围绕它来介绍,如果你还没有使用过MVP架构,Medium上有很多关于它的好文章,你可以去看看。

5.2 什么是MVVM模式?

MVVM模式是一种架构模式。它代表Model-View-ViewModel。我认为这个名称会使开发人员感到困惑。如果我来命名它的话,我会将其命名为View-ViewModel-Model,因为ViewModel是连接View和Model的中间人。

其中View是对你的Activity/Fragment/或者其他自定义View的抽象名称,请注意,不要将它与Android 的View混为一谈,这非常重要。View应该是干净的,在View中,不应该包含任何逻辑代码,也不应该持有任何数据,他应该持有一个ViewModel实例,所有的数据都应该从实例中去获取。此外,View应该观察这些数据,并且当ViewModel中的数据更改时,布局也应该刷新一次。总之,View的职责是:布局如何查找不同的数据和状态。

ViewModel是保存数据的类的抽象名称,并具有何时应获取数据以及应何时显示数据的逻辑。 ViewModel保持当前状态。此外,ViewModel应该保持一个或者多个Model实例,所有的数据都应该从这些Model实例获取。例如,ViewModel不应该知道数据是来自数据库还是远程服务器。此外,ViewModel完全不应该了解View。而且,ViewModel也完全不应该了解Android框架层的东西。

Model是数据层的抽象名称。这是我们将从远程服务器获取数据并将其缓存在内存中或保存在本地数据库中的类。但是请注意,这里的Model和CarUserSquare 这些model类是不一样,这些数据模型类仅仅只保持数据,而Model是Repository模式的实现,在后文将介绍,并且Model不应该了解ViewModel。

如果正确实施,MVVM是分离代码并使其更具可测试性的好方法。它有助于我们遵循SOLID原则,因此我们的代码更易于维护。

代码示例

现在,我将写一个最简单的例子来说明它是如何工作的

首先,让我们创建一个简单的Model,该Model返回一些字符串:

class RepoModel {
   

    fun refreshData() : String {
   
        return "Some new data"
    }
}

通常,获取数据是异步调用,因此我们必须等待加载数据。为了模拟它,我将类更改为以下内容:

class RepoModel {
   

    fun refreshData(onDataReadyCallback: OnDataReadyCallback) {
   
        Handler().postDelayed({
    onDataReadyCallback.onDataReady("new data") },2000)
    }
}

interface OnDataReadyCallback {
   
    fun onDataReady(data : String)
}

首先,我们创建了一个接口OnDataReadyCallback,它有一个方法onDataReady,然后将OnDataReadyCallback作为refreshData的参数,用Handler来模拟等待,当2000ms后,调用接口实例的onDataReady方法。

让我们看一下ViewModel:

class MainViewModel {
   
    var repoModel: RepoModel = RepoModel()
    var text: String = ""
    var isLoading: Boolean = false
}

如你所见,这里有一个RepoModel实例,一个我们要显示的 text,和一个保存状态的boolean值isLoading。现在,我们创建一个refresh方法,该方法负责获取数据

class MainViewModel {
   
    ...

    val onDataReadyCallback = object : OnDataReadyCallback {
   
        override fun onDataReady(data: String) {
   
            isLoading.set(false)
            text.set(data)
        }
    }

    fun refresh(){
   
        isLoading.set(true)
        repoModel.refreshData(onDataReadyCallback)
    }
}

refresh方法调用了repoModel的refreshData方法,传递了一个onDataReadyCallback。但是等一会,object是什么鬼?每当你要实现某个接口或扩展某些类而不创建子类时,都将使用对象声明。如果要使用它作为匿名类怎么办?在这种情况下,您必须使用对象表达式

class MainViewModel {
   
    var repoModel: RepoModel = RepoModel()
    var text: String = ""
    var isLoading: Boolean = false

    fun refresh() {
   
        repoModel.refreshData( object : OnDataReadyCallback {
   
        override fun onDataReady(data: String) {
   
            text = data
        })
    }
}

当我们调用refresh时,我们应该将视图更改为加载状态,一旦数据到来,就应该将isLoading设置为false

另外,我们应该将text更改为ObservableField <String>,并将isLoading更改为ObservableField <Boolean>。 ObservableField是Data Binding库中的一个类,我们可以使用它代替创建Observable对象。它包装了我们想要观察的对象。

class MainViewModel {
   
    var repoModel: RepoModel = RepoModel()

    val text = ObservableField<String>()

    val isLoading = ObservableField<Boolean>()

    fun refresh(){
   
        isLoading.set(true)
        repoModel.refreshData(object : OnDataReadyCallback {
   
            override fun onDataReady(data: String) {
   
                isLoading.set(false)
                text.set(data)
            }
        })
    }
}

注意,我使用val而不是var,因为我们仅更改字段中的值,而不更改字段本身,如果要初始化它,则应该执行以下操作:

 val text = ObservableField("old data")
 val isLoading = ObservableField(false)

我们更改布局,以让它可以观察textisLoading,首先,我们将绑定MainViewModel而不是Repository:

<data>
    <variable
        name="viewModel"
        type="me.mladenrakonjac.modernandroidapp.MainViewModel" />
</data>

然后,做一下操作:

  • 更改TextView以观察MainViewModel实例上的text
  • 添加仅在isLoadingtrue时可见的ProgressBar
  • 单击的add按钮将从MainViewModel实例调用refresh函数,并且仅在isLoadingfalse时才可单击
...

        <TextView
            android:id="@+id/repository_name"
            android:text="@{viewModel.text}"
            ...
            />

        ...
        <ProgressBar
            android:id="@+id/loading"
            android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}"
            ...
            />

        <Button
            android:id="@+id/refresh_button"
            android:onClick="@{() -> viewModel.refresh()}"
            android:clickable="@{viewModel.isLoading ? false : true}"
            />
...

如果此时你运行程序,将会报错,原因是,如果未导入View,则无法使用View.VISIBLEView.GONE。因此,我们必须导入它:

<data>
        <import type="android.view.View"/>

        <variable
            name="viewModel"
            type="me.fleka.modernandroidapp.MainViewModel" />
</data>

ok,布局就到此完成,接下来该完成绑定了,如我们所说,View应该持有一个ViewModel实例:

class MainActivity : AppCompatActivity() {
   

    lateinit var binding: ActivityMainBinding

    var mainViewModel = MainViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
   
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.viewModel = mainViewModel
        binding.executePendingBindings()

    }
}

最后,我们可以运行它了。

您可以看到旧数据已更改为新数据

这是最简单的MVVM示例。

对此有一个问题,让我们现在旋转手机:

新数据又变回了旧数据。这怎么可能呢?看一下Activity的生命周期:

旋转屏幕后,将创建Activity的新实例,并调用onCreate()方法。现在,看看我们的Activity:

class MainActivity : AppCompatActivity() {
   

    lateinit var binding: ActivityMainBinding

    var mainViewModel = MainViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
   
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.viewModel = mainViewModel
        binding.executePendingBindings()

    }
}

如您所见,一旦创建了一个新的Activity实例,便也会创建一个新的MainViewModel实例。如果以某种方式,我们可以为每个重新创建的MainActivity具有相同的MainViewModel实例,那会很好吗?

Lifecycle-aware 组件介绍

由于许多开发人员都遇到了这个问题,因此Android Framework Team的开发人员决定开发可帮助我们解决这个问题的库。 ViewModel类就是其中之一。它是我们所有ViewModels都应该扩展的类。

让我们的MainViewModel 继承自有生命周期感知的组件ViewModel,首先,我们应该在build.gradle文件中添加该生命周期感知组件库(译者注:版本不是最新,使用时更新最新版本):

dependencies {
   
    ... 

    implementation "android.arch.lifecycle:runtime:1.0.0-alpha9"
    implementation "android.arch.lifecycle:extensions:1.0.0-alpha9"
    kapt "android.arch.lifecycle:compiler:1.0.0-alpha9"
}

MainViewModel继承自ViewModel,如下:

package me.mladenrakonjac.modernandroidapp

import android.arch.lifecycle.ViewModel

class MainViewModel : ViewModel() {
   
    ...
}

在Activity的onCreate方法中,你应该改为:

class MainActivity : AppCompatActivity() {
   

    lateinit var binding: ActivityMainBinding
    
    override fun onCreate(savedInstanceState: Bundle?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值