如何在Android中使用Kotlin实现一个存储库

212 阅读7分钟

使用Kotlin在Android中实现存储库

存储库通常被认为是Android应用程序中的单一真理来源。换句话说,它作为一个特定数据源的抽象。存储库使应用程序能够消费数据而不必担心其来源。

一些常见的数据源包括本地数据库、缓存和在线服务器。使用存储库可以让开发者更有效地管理数据。由于业务逻辑与用户界面的分离,识别错误也更加容易。

App Architecture

目标

在本教程中,我们将在一个使用MVVM架构的Android应用程序中加入一个资源库。

前提条件

要继续学习,你需要对Kotlin有一些基本了解。此外,你应该已经在你的电脑上安装了[Android Studio]。

注意,该应用程序将从https://jsonplaceholder.typicode.com/posts

了解应用程序的数据

在构建任何应用程序时,第一步是了解数据源。这个阶段很关键,因为它可以让开发人员适当地构建应用程序组件。

在你的浏览器上导航到https://jsonplaceholder.typicode.com/posts

你会注意到,该链接以JSON 格式返回数据,如下图所示。

Data Format

上面的数据包括诸如userID,id,title, 和body 等变量。我们将需要在应用程序中声明这些变量。

开始使用

打开Android Studio ,生成一个新项目。选择一个empty 模板,然后点击finish 。你将需要耐心等待,因为这个过程可能会很耗时。

当项目完成后,Android Studio将在一个新窗口中显示所有需要的文件。

在进一步行动之前,我们需要向我们的应用程序添加几个权限和依赖。

导航到Manifests/AndroidManifest.xml 文件,并包括以下一行以启用互联网访问。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wanja.myapplication">
    <uses-permission android:name="android.permission.INTERNET"/>  <!--internet permission-->

接下来,打开应用程序级别的build.gradle 文件,并添加以下依赖项。

dependencies {
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation 'androidx.cardview:cardview:1.0.0'
    def lifecycle_version = "2.3.1"
    def arch_version = "2.1.0"
    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
    // Lifecycles only (without ViewModel or LiveData)
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
    // Saved state module for ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
    // Annotation processor
    kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
    // alternately - if using Java8, use the following instead of lifecycle-compiler
    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
    //volley
    implementation 'com.android.volley:volley:1.2.0'
}

在上面的代码中,我们正在导入LifecycleVolley 依赖项。Lifecycle依赖项负责MVVM架构。它允许我们使用LiveData和ViewModels等元素。

我们将使用Volley 库来执行对https://jsonplaceholder.typicode.com/posts 的网络请求。

记得在应用级build.gradle 文件的顶部添加kotlin-kapt ,如下图所示。

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

创建数据类

一个数据类允许我们定义变量和它们的值。如前所述,该应用程序有四个主要的变量;userId,id,title, 和body

在主包目录下,创建一个新的类,并将其命名为Post

Post 文件中添加以下代码。

data class Post(val userId: Int, val id: Int, val title: String, val body: String)

正如上面所强调的,Post 类将把userID,id,title, 和body 作为其参数。注意,getterssetters 将被自动生成。

创建资源库

存储库允许应用程序连接到不同的数据源。这些数据然后被发送到主用户界面。

main 目录中,生成一个新的文件,并将其命名为MainRepository

我们将从ArrayList中检索数据,以及从互联网上检索。Volley需要一个RequestQueue来执行网络请求。因此,我们将在MainRepository 类中加入一个RequestQueue 作为参数。

class MainRepository(var mRequestQueue: RequestQueue) {
}

接下来,我们需要在应用程序中添加一个MutableLiveData 对象。

这个对象将持有将从互联网上检索的数据。

 var posts = MutableLiveData<ArrayList<Post>>() //this MutableLiveData will hold an ArrayList of posts

让我们创建一个方法,返回一个预先填充的Arraylist

它将模拟从本地存储中检索的数据。

    fun getData(): ArrayList<Post>{ //this method returns an arraylist
        var lst = ArrayList<Post>();
        var post1 = Post(1, 1, "Tom", "Test data")
        var post2 = Post(1, 1, "Thomas", "Tuesday")
        var post3 = Post(1, 1, "Tim", "Going to Mars")
        var post4 = Post(1, 1, "Theranos", "Big five animals")
        lst.add(post1)
        lst.add(post2)
        lst.add(post3)
        lst.add(post4)
        return lst
    }

我们还需要包括一个从互联网上检索数据的方法,如下所示。

 fun fetchOnlineData(){
        val url = "https://jsonplaceholder.typicode.com/posts"
        var onlineposts = ArrayList<Post>();

        val jsonArrayRequest = JsonArrayRequest(
            Request.Method.GET, url, null,
            Response.Listener
            { response -> //retrieved data is stored in this response object
                for(a in 0 until  response.length()){ 
                    //this for loop iterates through the retrieved arraylist
                    val obj = response.getJSONObject(a)
                   
                    //retrieve specific variables
                    val userId = obj.getInt("userId") 
                    val id = obj.getInt("id")
                    val title = obj.getString("title")
                    val body = obj.getString("body")

                    var post = Post(userId,id,title,body)
                    onlineposts.add(post) //adding Post objects to an arraylist

                }
                posts.value = onlineposts //updates the mutablelivedata with the retrieved data

            },
            { error ->
                // We handle any errors here
            }
        )
        // Access the RequestQueue through your singleton class.]
        mRequestQueue.add(jsonArrayRequest) 
        //we use the request queue specified in the class contructor.
    }

下面是MainRepository 类的全部代码。

import android.util.Log
import androidx.lifecycle.MutableLiveData
import com.android.volley.Request
import com.android.volley.RequestQueue
import com.android.volley.Response
import com.android.volley.toolbox.JsonArrayRequest
import com.android.volley.toolbox.JsonObjectRequest

class MainRepository(var mRequestQueue: RequestQueue) {
    var posts = MutableLiveData<ArrayList<Post>>()

    fun getData(): ArrayList<Post>{ //local data source
        var lst = ArrayList<Post>();
        var post1 = Post(1, 1, "Tom", "Test data")
        var post2 = Post(1, 1, "Thomas", "Tuesday")
        var post3 = Post(1, 1, "Tim", "Going to Mars")
        var post4 = Post(1, 1, "Theranos", "Big five animals")
        lst.add(post1)
        lst.add(post2)
        lst.add(post3)
        lst.add(post4)
        return lst
    }

    fun fetchOnlineData(){ //online data source
        val url = "https://jsonplaceholder.typicode.com/posts" //our online data source
        var onlineposts = ArrayList<Post>();

        val jsonArrayRequest = JsonArrayRequest(
            Request.Method.GET, url, null,
            Response.Listener
            { response ->
                for(a in 0 until  response.length()){
                    val obj = response.getJSONObject(a)

                    val userId = obj.getInt("userId")
                    val id = obj.getInt("id")
                    val title = obj.getString("title")
                    val body = obj.getString("body")

                    var post = Post(userId,id,title,body)
                    onlineposts.add(post)

                }
                posts.value = onlineposts

            },
            { error ->
                // TODO: Handle error
            }
        )
        // Access the RequestQueue through your singleton class.]
        mRequestQueue.add(jsonArrayRequest)
    }

}

创建ViewModel

在这一步,我们将把上面的MainRepository 与我们的ViewModel

在主包目录下创建一个新文件,并将其命名为MainViewModel

在生成的MainViewModel 文件中添加以下代码。

class MainViewModel(var mRequestQueue: RequestQueue) : ViewModel() {
    //we include a Volley requestquest as a parameter

    var localposts =  MutableLiveData<ArrayList<Post>>() //this mutable will store local data
    var onlineposts =  MutableLiveData<ArrayList<Post>>() //stores data retrieved from server
    var mainRepository = MainRepository(mRequestQueue) //initializing the MainRepository

    init{ 
        localposts.value = mainRepository.getData()  //fetching local data
        mainRepository.fetchOnlineData() //fetching online data
        onlineposts = mainRepository.posts //updating the onlineposts array with new data
    }

}

在上面的代码中,我们在类的构造函数中包含了一个RequestQueue 。如前所述,RequestQueue 允许我们执行一个Volley 网络请求。

我们还定义了localpostsonlineposts 数组。这些组件将存储我们的应用数据。

最后一件事是初始化MainRepository 。我们在这个类中把RequestQueue 作为一个参数传递。

每当ViewModel被初始化时,init 函数就会被调用。因此,它是获取和加载数据的完美场所。

创建一个ViewModelFactory

一个ViewModelFactory ,允许我们在ViewModel ,每当它被初始化时传递某些值。在我们的例子中,我们需要在ViewModel的构造函数中传递一个RequestQueue

在主目录中,创建一个名为MainViewModelFactory 的文件。

在生成的MainViewModelFactory 中添加以下代码。

class MainViewModelFactory(var mRequestQueue: RequestQueue): Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if(modelClass.isAssignableFrom(MainViewModel::class.java)){
            return MainViewModel(mRequestQueue) as T
        }
        throw IllegalArgumentException("Unknown class")
    }
}

如上所示,MainViewModelFactory 在初始化时需要一个RequestQueue 。然后这个组件被传递给MainViewModel

如果没有找到MainViewModelMainViewModelFactory 将抛出一个IllegalArgumentException

生成用户界面

UI使我们能够向用户显示信息。例如,在我们的案例中,用户将同时查看本地和在线数据。

我们将使用一个简单的用户界面来向用户展示数据。

导航到res/layout 文件夹,添加下面的代码。

    <TextView
        android:id="@+id/content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

检索到的数据将显示在上面突出显示的TextView 。确保你为这个TextView分配一个id

完成MainActivity

这是我们应用程序的最后阶段。我们需要连接所有我们在这个活动中讨论过的组件。

MainActivity 文件中,声明以下变量。

    private lateinit var mainViewModel: MainViewModel  //this is our viewmodel
    private lateinit var mainViewModelFactory: MainViewModelFactory // the viewmodelfactory
    private lateinit var mRequestQueue: RequestQueue //requestqueue

lateinit 关键字使我们能够声明变量而不给它们赋值。

我们现在需要给上述变量赋值,如下图所示。

mRequestQueue = Volley.newRequestQueue(this) //assigning the requestqueue
mainViewModelFactory = MainViewModelFactory(mRequestQueue) 
//passing the requestque to the viewmodel factory
mainViewModel = ViewModelProviders.of(this, mainViewModelFactory).get(MainViewModel::class.java)
//initializing the viewmodel
``

The next step is to load the data into the `TextView`.

```kt
content.text = "Local Data\n"+mainViewModel.localposts.value!!.toString()

在上面的代码中,content 是我们分配给TextView的id

我们使用mainViewModel.localposts.value!! ,访问存储本地数据的ArrayList。然后使用toString() 方法将数据转换为string

下一步是观察存储我们在线数据的ArrayList。我们只有在应用程序完成获取数据后才会更新用户界面。

mainViewModel.onlineposts.observeForever { //observing the mutablelivedata
            if(it.isEmpty()){ //checking if the arraylist is empty
                //do something
            }else{ //displaying data when the arraylist is not empty
                content.text = "Online Data\n"+it.toString() 
                //convert the array into a string
            }
        }

下面是整个MainActivity 的代码。

class MainActivity : AppCompatActivity() {
    private lateinit var mainViewModel: MainViewModel
    private lateinit var mainViewModelFactory: MainViewModelFactory
    private lateinit var mRequestQueue: RequestQueue

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mRequestQueue = Volley.newRequestQueue(this)
        mainViewModelFactory = MainViewModelFactory(mRequestQueue)
        mainViewModel = ViewModelProviders.of(this, mainViewModelFactory).get(MainViewModel::class.java)

        content.text = "Local Data\n"+mainViewModel.localposts.value!!.toString()

        mainViewModel.onlineposts.observeForever {
            if(it.isEmpty()){
                //do something
            }else{
                content.text = "Online Data\n"+it.toString()
            }
        }


    }

}

当你测试该应用程序时,它应该显示本地数据,然后用在线帖子更新用户界面。

总结

本文讨论了如何使用Kotlin在Android应用程序中实现一个资源库。仓库的一个巨大优势是,它支持业务逻辑的分离,从而提高生产力。

因此,你可以使用本课程中获得的知识和技能来制作高质量的Android应用程序。