使用Kotlin在Android中实现存储库
存储库通常被认为是Android应用程序中的单一真理来源。换句话说,它作为一个特定数据源的抽象。存储库使应用程序能够消费数据而不必担心其来源。
一些常见的数据源包括本地数据库、缓存和在线服务器。使用存储库可以让开发者更有效地管理数据。由于业务逻辑与用户界面的分离,识别错误也更加容易。

目标
在本教程中,我们将在一个使用MVVM架构的Android应用程序中加入一个资源库。
前提条件
要继续学习,你需要对Kotlin有一些基本了解。此外,你应该已经在你的电脑上安装了[Android Studio]。
注意,该应用程序将从
https://jsonplaceholder.typicode.com/posts。
了解应用程序的数据
在构建任何应用程序时,第一步是了解数据源。这个阶段很关键,因为它可以让开发人员适当地构建应用程序组件。
在你的浏览器上导航到https://jsonplaceholder.typicode.com/posts 。
你会注意到,该链接以JSON 格式返回数据,如下图所示。

上面的数据包括诸如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'
}
在上面的代码中,我们正在导入Lifecycle 和Volley 依赖项。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 作为其参数。注意,getters 和setters 将被自动生成。
创建资源库
存储库允许应用程序连接到不同的数据源。这些数据然后被发送到主用户界面。
在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 网络请求。
我们还定义了localposts 和onlineposts 数组。这些组件将存储我们的应用数据。
最后一件事是初始化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 。
如果没有找到MainViewModel ,MainViewModelFactory 将抛出一个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应用程序。