[Android kotlin]如何使用Kotlin 协程进行
关于这个项目:
- 如果您想开始使用 Kotlin Coroutines for Android Development 并想掌握它,那么这个项目适合您。
- 本项目已实现 Android 中 Kotlin Coroutines 的常见用例。
- 另外,学习为使用 Kotlin 协程的 ViewModel 编写单元测试。
- 这是您学习 Kotlin Coroutines for Android Development 的一站式解决方案。
通过该项目中的示例学习 Kotlin Coroutines 的步骤
- 首先,学习 Kotlin Coroutines 的概念
- 然后,只需克隆、构建、运行项目并通过示例开始学习 Kotlin 协程。
这个 Kotlin Coroutines 示例项目将帮助您学习以下 Android 应用程序开发:
- 什么是 Kotlin 协程?
- 如何在 Android 应用开发中使用 Kotlin 协程?
- 关于如何在 Android 中实现 Kotlin 协程的分步指南。
- 使用 Kotlin Coroutines 在后台执行简单任务。
- 使用 Kotlin Coroutines 执行系列任务。
- 使用 Kotlin 协程并行执行任务。
- 使用 Kotlin 协程并行进行两个网络调用。
- Kotlin 协程中的作用域是什么?
- 使用 Kotlin Coroutines 取消后台任务。
- Kotlin 协程中的异常处理。
- 使用带有改造的 Kotlin 协程。
- 将 Kotlin 协程与 Room 数据库结合使用。
- 将 Kotlin Coroutines 与各种 3rd 方库一起使用。
- 使用 Kotlin 协程为任务添加超时。
- 为使用 Kotlin 协程的 ViewModel 编写单元测试。
用于 Android 开发的 Kotlin 协程示例:
-
单一网络调用:了解如何使用 Kotlin 协程进行网络调用。这是 Android 应用程序开发中一个非常简单的用例。
- 活动代码
package com.mindorks.example.coroutines.learn.retrofit.single
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.mindorks.example.coroutines.R
import com.mindorks.example.coroutines.data.api.ApiHelperImpl
import com.mindorks.example.coroutines.data.api.RetrofitBuilder
import com.mindorks.example.coroutines.data.local.DatabaseBuilder
import com.mindorks.example.coroutines.data.local.DatabaseHelperImpl
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.learn.base.ApiUserAdapter
import com.mindorks.example.coroutines.utils.Status
import com.mindorks.example.coroutines.utils.ViewModelFactory
import kotlinx.android.synthetic.main.activity_recycler_view.*
class SingleNetworkCallActivity : AppCompatActivity() {
private lateinit var viewModel: SingleNetworkCallViewModel
private lateinit var adapter: ApiUserAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycler_view)
setupUI()
setupViewModel()
setupObserver()
}
private fun setupUI() {
recyclerView.layoutManager = LinearLayoutManager(this)
adapter =
ApiUserAdapter(
arrayListOf()
)
recyclerView.addItemDecoration(
DividerItemDecoration(
recyclerView.context,
(recyclerView.layoutManager as LinearLayoutManager).orientation
)
)
recyclerView.adapter = adapter
}
private fun setupObserver() {
viewModel.getUsers().observe(this, {
when (it.status) {
Status.SUCCESS -> {
progressBar.visibility = View.GONE
it.data?.let { users -> renderList(users) }
recyclerView.visibility = View.VISIBLE
}
Status.LOADING -> {
progressBar.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
}
Status.ERROR -> {
//Handle Error
progressBar.visibility = View.GONE
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
}
}
})
}
private fun renderList(users: List<ApiUser>) {
adapter.addData(users)
adapter.notifyDataSetChanged()
}
private fun setupViewModel() {
viewModel = ViewModelProviders.of(
this,
ViewModelFactory(
ApiHelperImpl(RetrofitBuilder.apiService),
DatabaseHelperImpl(DatabaseBuilder.getInstance(applicationContext))
)
).get(SingleNetworkCallViewModel::class.java)
}
}
- 视图模型代码
package com.mindorks.example.coroutines.learn.retrofit.single
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mindorks.example.coroutines.data.api.ApiHelper
import com.mindorks.example.coroutines.data.local.DatabaseHelper
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.utils.Resource
import kotlinx.coroutines.launch
class SingleNetworkCallViewModel(
private val apiHelper: ApiHelper,
private val dbHelper: DatabaseHelper
) : ViewModel() {
private val users = MutableLiveData<Resource<List<ApiUser>>>()
init {
fetchUsers()
}
private fun fetchUsers() {
viewModelScope.launch {
users.postValue(Resource.loading(null))
try {
val usersFromApi = apiHelper.getUsers()
users.postValue(Resource.success(usersFromApi))
} catch (e: Exception) {
users.postValue(Resource.error(e.toString(), null))
}
}
}
fun getUsers(): LiveData<Resource<List<ApiUser>>> {
return users
}
}
-
系列网络调用:了解如何使用 Kotlin 协程进行系列网络调用。当您要进行依赖于另一个网络调用的网络调用时,这很有用。
- 活动代码
package com.mindorks.example.coroutines.learn.retrofit.series
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.mindorks.example.coroutines.R
import com.mindorks.example.coroutines.data.api.ApiHelperImpl
import com.mindorks.example.coroutines.data.api.RetrofitBuilder
import com.mindorks.example.coroutines.data.local.DatabaseBuilder
import com.mindorks.example.coroutines.data.local.DatabaseHelperImpl
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.learn.base.ApiUserAdapter
import com.mindorks.example.coroutines.utils.Status
import com.mindorks.example.coroutines.utils.ViewModelFactory
import kotlinx.android.synthetic.main.activity_recycler_view.*
class SeriesNetworkCallsActivity : AppCompatActivity() {
private lateinit var viewModel: SeriesNetworkCallsViewModel
private lateinit var adapter: ApiUserAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycler_view)
setupUI()
setupViewModel()
setupObserver()
}
private fun setupUI() {
recyclerView.layoutManager = LinearLayoutManager(this)
adapter =
ApiUserAdapter(
arrayListOf()
)
recyclerView.addItemDecoration(
DividerItemDecoration(
recyclerView.context,
(recyclerView.layoutManager as LinearLayoutManager).orientation
)
)
recyclerView.adapter = adapter
}
private fun setupObserver() {
viewModel.getUsers().observe(this, {
when (it.status) {
Status.SUCCESS -> {
progressBar.visibility = View.GONE
it.data?.let { users -> renderList(users) }
recyclerView.visibility = View.VISIBLE
}
Status.LOADING -> {
progressBar.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
}
Status.ERROR -> {
//Handle Error
progressBar.visibility = View.GONE
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
}
}
})
}
private fun renderList(users: List<ApiUser>) {
adapter.addData(users)
adapter.notifyDataSetChanged()
}
private fun setupViewModel() {
viewModel = ViewModelProviders.of(
this,
ViewModelFactory(
ApiHelperImpl(RetrofitBuilder.apiService),
DatabaseHelperImpl(DatabaseBuilder.getInstance(applicationContext))
)
).get(SeriesNetworkCallsViewModel::class.java)
}
}
- 视图模型代码
package com.mindorks.example.coroutines.learn.retrofit.series
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mindorks.example.coroutines.data.api.ApiHelper
import com.mindorks.example.coroutines.data.local.DatabaseHelper
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.utils.Resource
import kotlinx.coroutines.launch
class SeriesNetworkCallsViewModel(
private val apiHelper: ApiHelper,
private val dbHelper: DatabaseHelper
) : ViewModel() {
private val users = MutableLiveData<Resource<List<ApiUser>>>()
init {
fetchUsers()
}
private fun fetchUsers() {
viewModelScope.launch {
users.postValue(Resource.loading(null))
try {
val usersFromApi = apiHelper.getUsers()
val moreUsersFromApi = apiHelper.getMoreUsers()
val allUsersFromApi = mutableListOf<ApiUser>()
allUsersFromApi.addAll(usersFromApi)
allUsersFromApi.addAll(moreUsersFromApi)
users.postValue(Resource.success(allUsersFromApi))
} catch (e: Exception) {
users.postValue(Resource.error("Something Went Wrong", null))
}
}
}
fun getUsers(): LiveData<Resource<List<ApiUser>>> {
return users
}
}
-
并行网络调用:了解如何使用 Kotlin 协程并行进行网络调用。当您想要并行进行彼此独立的网络调用时,这很有用。
- 活动代码
package com.mindorks.example.coroutines.learn.retrofit.parallel
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.mindorks.example.coroutines.R
import com.mindorks.example.coroutines.data.api.ApiHelperImpl
import com.mindorks.example.coroutines.data.api.RetrofitBuilder
import com.mindorks.example.coroutines.data.local.DatabaseBuilder
import com.mindorks.example.coroutines.data.local.DatabaseHelperImpl
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.learn.base.ApiUserAdapter
import com.mindorks.example.coroutines.utils.Status
import com.mindorks.example.coroutines.utils.ViewModelFactory
import kotlinx.android.synthetic.main.activity_recycler_view.*
class ParallelNetworkCallsActivity : AppCompatActivity() {
private lateinit var viewModel: ParallelNetworkCallsViewModel
private lateinit var adapter: ApiUserAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycler_view)
setupUI()
setupViewModel()
setupObserver()
}
private fun setupUI() {
recyclerView.layoutManager = LinearLayoutManager(this)
adapter =
ApiUserAdapter(
arrayListOf()
)
recyclerView.addItemDecoration(
DividerItemDecoration(
recyclerView.context,
(recyclerView.layoutManager as LinearLayoutManager).orientation
)
)
recyclerView.adapter = adapter
}
private fun setupObserver() {
viewModel.getUsers().observe(this, {
when (it.status) {
Status.SUCCESS -> {
progressBar.visibility = View.GONE
it.data?.let { users -> renderList(users) }
recyclerView.visibility = View.VISIBLE
}
Status.LOADING -> {
progressBar.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
}
Status.ERROR -> {
//Handle Error
progressBar.visibility = View.GONE
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
}
}
})
}
private fun renderList(users: List<ApiUser>) {
adapter.addData(users)
adapter.notifyDataSetChanged()
}
private fun setupViewModel() {
viewModel = ViewModelProviders.of(
this,
ViewModelFactory(
ApiHelperImpl(RetrofitBuilder.apiService),
DatabaseHelperImpl(DatabaseBuilder.getInstance(applicationContext))
)
).get(ParallelNetworkCallsViewModel::class.java)
}
}
- 视图模型代码
package com.mindorks.example.coroutines.learn.retrofit.parallel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mindorks.example.coroutines.data.api.ApiHelper
import com.mindorks.example.coroutines.data.local.DatabaseHelper
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.utils.Resource
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
class ParallelNetworkCallsViewModel(
private val apiHelper: ApiHelper,
private val dbHelper: DatabaseHelper
) : ViewModel() {
private val users = MutableLiveData<Resource<List<ApiUser>>>()
init {
fetchUsers()
}
private fun fetchUsers() {
viewModelScope.launch {
users.postValue(Resource.loading(null))
try {
// coroutineScope is needed, else in case of any network error, it will crash
coroutineScope {
val usersFromApiDeferred = async { apiHelper.getUsers() }
val moreUsersFromApiDeferred = async { apiHelper.getMoreUsers() }
val usersFromApi = usersFromApiDeferred.await()
val moreUsersFromApi = moreUsersFromApiDeferred.await()
val allUsersFromApi = mutableListOf<ApiUser>()
allUsersFromApi.addAll(usersFromApi)
allUsersFromApi.addAll(moreUsersFromApi)
users.postValue(Resource.success(allUsersFromApi))
}
} catch (e: Exception) {
users.postValue(Resource.error("Something Went Wrong", null))
}
}
}
fun getUsers(): LiveData<Resource<List<ApiUser>>> {
return users
}
}
-
房间数据库操作:了解如何使用 Kotlin 协程在数据库中获取或插入实体。当您在 Android 应用程序中使用房间数据库时,这很有用。
- 活动代码
package com.mindorks.example.coroutines.learn.room
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.mindorks.example.coroutines.R
import com.mindorks.example.coroutines.data.api.ApiHelperImpl
import com.mindorks.example.coroutines.data.api.RetrofitBuilder
import com.mindorks.example.coroutines.data.local.DatabaseBuilder
import com.mindorks.example.coroutines.data.local.DatabaseHelperImpl
import com.mindorks.example.coroutines.data.local.entity.User
import com.mindorks.example.coroutines.learn.base.UserAdapter
import com.mindorks.example.coroutines.utils.Status
import com.mindorks.example.coroutines.utils.ViewModelFactory
import kotlinx.android.synthetic.main.activity_recycler_view.*
class RoomDBActivity : AppCompatActivity() {
private lateinit var viewModel: RoomDBViewModel
private lateinit var adapter: UserAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycler_view)
setupUI()
setupViewModel()
setupObserver()
}
private fun setupUI() {
recyclerView.layoutManager = LinearLayoutManager(this)
adapter =
UserAdapter(
arrayListOf()
)
recyclerView.addItemDecoration(
DividerItemDecoration(
recyclerView.context,
(recyclerView.layoutManager as LinearLayoutManager).orientation
)
)
recyclerView.adapter = adapter
}
private fun setupObserver() {
viewModel.getUsers().observe(this, {
when (it.status) {
Status.SUCCESS -> {
progressBar.visibility = View.GONE
it.data?.let { users -> renderList(users) }
recyclerView.visibility = View.VISIBLE
}
Status.LOADING -> {
progressBar.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
}
Status.ERROR -> {
//Handle Error
progressBar.visibility = View.GONE
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
}
}
})
}
private fun renderList(users: List<User>) {
adapter.addData(users)
adapter.notifyDataSetChanged()
}
private fun setupViewModel() {
viewModel = ViewModelProviders.of(
this,
ViewModelFactory(
ApiHelperImpl(RetrofitBuilder.apiService),
DatabaseHelperImpl(DatabaseBuilder.getInstance(applicationContext))
)
).get(RoomDBViewModel::class.java)
}
}
- 视图模型代码
package com.mindorks.example.coroutines.learn.room
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mindorks.example.coroutines.data.api.ApiHelper
import com.mindorks.example.coroutines.data.local.DatabaseHelper
import com.mindorks.example.coroutines.data.local.entity.User
import com.mindorks.example.coroutines.utils.Resource
import kotlinx.coroutines.launch
class RoomDBViewModel(private val apiHelper: ApiHelper, private val dbHelper: DatabaseHelper) :
ViewModel() {
private val users = MutableLiveData<Resource<List<User>>>()
init {
fetchUsers()
}
private fun fetchUsers() {
viewModelScope.launch {
users.postValue(Resource.loading(null))
try {
val usersFromDb = dbHelper.getUsers()
if (usersFromDb.isEmpty()) {
val usersFromApi = apiHelper.getUsers()
val usersToInsertInDB = mutableListOf<User>()
for (apiUser in usersFromApi) {
val user = User(
apiUser.id,
apiUser.name,
apiUser.email,
apiUser.avatar
)
usersToInsertInDB.add(user)
}
dbHelper.insertAll(usersToInsertInDB)
users.postValue(Resource.success(usersToInsertInDB))
} else {
users.postValue(Resource.success(usersFromDb))
}
} catch (e: Exception) {
users.postValue(Resource.error("Something Went Wrong", null))
}
}
}
fun getUsers(): LiveData<Resource<List<User>>> {
return users
}
}
-
长时间运行的任务:了解如何使用 Kotlin 协程执行长时间运行的任务。如果您想使用 Kotlin 协程在后台线程中执行任何任务,那么这很有用。
- 活动代码
package com.mindorks.example.coroutines.learn.task.onetask
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProviders
import com.mindorks.example.coroutines.R
import com.mindorks.example.coroutines.data.api.ApiHelperImpl
import com.mindorks.example.coroutines.data.api.RetrofitBuilder
import com.mindorks.example.coroutines.data.local.DatabaseBuilder
import com.mindorks.example.coroutines.data.local.DatabaseHelperImpl
import com.mindorks.example.coroutines.utils.Status
import com.mindorks.example.coroutines.utils.ViewModelFactory
import kotlinx.android.synthetic.main.activity_long_running_task.*
import kotlinx.android.synthetic.main.activity_recycler_view.progressBar
class LongRunningTaskActivity : AppCompatActivity() {
private lateinit var viewModel: LongRunningTaskViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_long_running_task)
setupViewModel()
setupLongRunningTask()
}
private fun setupLongRunningTask() {
viewModel.getStatus().observe(this, {
when (it.status) {
Status.SUCCESS -> {
progressBar.visibility = View.GONE
textView.text = it.data
textView.visibility = View.VISIBLE
}
Status.LOADING -> {
progressBar.visibility = View.VISIBLE
textView.visibility = View.GONE
}
Status.ERROR -> {
//Handle Error
progressBar.visibility = View.GONE
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
}
}
})
viewModel.startLongRunningTask()
}
private fun setupViewModel() {
viewModel = ViewModelProviders.of(
this,
ViewModelFactory(
ApiHelperImpl(RetrofitBuilder.apiService),
DatabaseHelperImpl(DatabaseBuilder.getInstance(applicationContext))
)
).get(LongRunningTaskViewModel::class.java)
}
}
- 视图模型代码
package com.mindorks.example.coroutines.learn.task.onetask
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mindorks.example.coroutines.data.api.ApiHelper
import com.mindorks.example.coroutines.data.local.DatabaseHelper
import com.mindorks.example.coroutines.utils.Resource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LongRunningTaskViewModel(
private val apiHelper: ApiHelper,
private val dbHelper: DatabaseHelper
) : ViewModel() {
private val status = MutableLiveData<Resource<String>>()
fun startLongRunningTask() {
viewModelScope.launch {
status.postValue(Resource.loading(null))
try {
// do a long running task
doLongRunningTask()
status.postValue(Resource.success("Task Completed"))
} catch (e: Exception) {
status.postValue(Resource.error("Something Went Wrong", null))
}
}
}
fun getStatus(): LiveData<Resource<String>> {
return status
}
private suspend fun doLongRunningTask() {
withContext(Dispatchers.Default) {
// your code for doing a long running task
// Added delay to simulate
delay(5000)
}
}
}
-
两个长时间运行的任务:了解如何使用 Kotlin 协程并行运行两个长时间运行的任务。
- 活动代码
package com.mindorks.example.coroutines.learn.task.twotasks
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProviders
import com.mindorks.example.coroutines.R
import com.mindorks.example.coroutines.data.api.ApiHelperImpl
import com.mindorks.example.coroutines.data.api.RetrofitBuilder
import com.mindorks.example.coroutines.data.local.DatabaseBuilder
import com.mindorks.example.coroutines.data.local.DatabaseHelperImpl
import com.mindorks.example.coroutines.utils.Status
import com.mindorks.example.coroutines.utils.ViewModelFactory
import kotlinx.android.synthetic.main.activity_long_running_task.*
import kotlinx.android.synthetic.main.activity_recycler_view.progressBar
class TwoLongRunningTasksActivity : AppCompatActivity() {
private lateinit var viewModel: TwoLongRunningTasksViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_long_running_task)
setupViewModel()
setupLongRunningTask()
}
private fun setupLongRunningTask() {
viewModel.getStatus().observe(this, {
when (it.status) {
Status.SUCCESS -> {
progressBar.visibility = View.GONE
textView.text = it.data
textView.visibility = View.VISIBLE
}
Status.LOADING -> {
progressBar.visibility = View.VISIBLE
textView.visibility = View.GONE
}
Status.ERROR -> {
//Handle Error
progressBar.visibility = View.GONE
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
}
}
})
viewModel.startLongRunningTask()
}
private fun setupViewModel() {
viewModel = ViewModelProviders.of(
this,
ViewModelFactory(
ApiHelperImpl(RetrofitBuilder.apiService),
DatabaseHelperImpl(DatabaseBuilder.getInstance(applicationContext))
)
).get(TwoLongRunningTasksViewModel::class.java)
}
}
- 视图模型代码
package com.mindorks.example.coroutines.learn.task.twotasks
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mindorks.example.coroutines.data.api.ApiHelper
import com.mindorks.example.coroutines.data.local.DatabaseHelper
import com.mindorks.example.coroutines.utils.Resource
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class TwoLongRunningTasksViewModel(
private val apiHelper: ApiHelper,
private val dbHelper: DatabaseHelper
) : ViewModel() {
private val status = MutableLiveData<Resource<String>>()
fun startLongRunningTask() {
viewModelScope.launch {
status.postValue(Resource.loading(null))
try {
// do long running tasks
val resultOneDeferred = async { doLongRunningTaskOne() }
val resultTwoDeferred = async { doLongRunningTaskTwo() }
val combinedResult = resultOneDeferred.await() + resultTwoDeferred.await()
status.postValue(Resource.success("Task Completed : $combinedResult"))
} catch (e: Exception) {
status.postValue(Resource.error("Something Went Wrong", null))
}
}
}
fun getStatus(): LiveData<Resource<String>> {
return status
}
private suspend fun doLongRunningTaskOne(): String {
delay(5000)
return "One"
}
private suspend fun doLongRunningTaskTwo(): String {
delay(5000)
return "Two"
}
}
-
超时:了解如何使用 Kotlin 协程为任务添加超时。如果您想为 Android 中的任何后台任务添加超时,这将非常有用。
- 活动代码
package com.mindorks.example.coroutines.learn.timeout
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.mindorks.example.coroutines.R
import com.mindorks.example.coroutines.data.api.ApiHelperImpl
import com.mindorks.example.coroutines.data.api.RetrofitBuilder
import com.mindorks.example.coroutines.data.local.DatabaseBuilder
import com.mindorks.example.coroutines.data.local.DatabaseHelperImpl
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.learn.base.ApiUserAdapter
import com.mindorks.example.coroutines.utils.Status
import com.mindorks.example.coroutines.utils.ViewModelFactory
import kotlinx.android.synthetic.main.activity_recycler_view.*
class TimeoutActivity : AppCompatActivity() {
private lateinit var viewModel: TimeoutViewModel
private lateinit var adapter: ApiUserAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycler_view)
setupUI()
setupViewModel()
setupObserver()
}
private fun setupUI() {
recyclerView.layoutManager = LinearLayoutManager(this)
adapter =
ApiUserAdapter(
arrayListOf()
)
recyclerView.addItemDecoration(
DividerItemDecoration(
recyclerView.context,
(recyclerView.layoutManager as LinearLayoutManager).orientation
)
)
recyclerView.adapter = adapter
}
private fun setupObserver() {
viewModel.getUsers().observe(this, {
when (it.status) {
Status.SUCCESS -> {
progressBar.visibility = View.GONE
it.data?.let { users -> renderList(users) }
recyclerView.visibility = View.VISIBLE
}
Status.LOADING -> {
progressBar.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
}
Status.ERROR -> {
//Handle Error
progressBar.visibility = View.GONE
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
}
}
})
}
private fun renderList(users: List<ApiUser>) {
adapter.addData(users)
adapter.notifyDataSetChanged()
}
private fun setupViewModel() {
viewModel = ViewModelProviders.of(
this,
ViewModelFactory(
ApiHelperImpl(RetrofitBuilder.apiService),
DatabaseHelperImpl(DatabaseBuilder.getInstance(applicationContext))
)
).get(TimeoutViewModel::class.java)
}
}
- 视图模型代码
package com.mindorks.example.coroutines.learn.timeout
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mindorks.example.coroutines.data.api.ApiHelper
import com.mindorks.example.coroutines.data.local.DatabaseHelper
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.utils.Resource
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
class TimeoutViewModel(
private val apiHelper: ApiHelper,
private val dbHelper: DatabaseHelper
) : ViewModel() {
private val users = MutableLiveData<Resource<List<ApiUser>>>()
init {
fetchUsers()
}
private fun fetchUsers() {
viewModelScope.launch {
users.postValue(Resource.loading(null))
try {
withTimeout(100) {
val usersFromApi = apiHelper.getUsers()
users.postValue(Resource.success(usersFromApi))
}
} catch (e: TimeoutCancellationException) {
users.postValue(Resource.error("TimeoutCancellationException", null))
} catch (e: Exception) {
users.postValue(Resource.error("Something Went Wrong", null))
}
}
}
fun getUsers(): LiveData<Resource<List<ApiUser>>> {
return users
}
}
-
Try-Catch 错误处理:了解如何使用 Try-Catch 处理 Kotlin 协程中的错误。
- 活动代码
package com.mindorks.example.coroutines.learn.errorhandling.trycatch
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.mindorks.example.coroutines.R
import com.mindorks.example.coroutines.data.api.ApiHelperImpl
import com.mindorks.example.coroutines.data.api.RetrofitBuilder
import com.mindorks.example.coroutines.data.local.DatabaseBuilder
import com.mindorks.example.coroutines.data.local.DatabaseHelperImpl
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.learn.base.ApiUserAdapter
import com.mindorks.example.coroutines.utils.Status
import com.mindorks.example.coroutines.utils.ViewModelFactory
import kotlinx.android.synthetic.main.activity_recycler_view.*
class TryCatchActivity : AppCompatActivity() {
private lateinit var viewModel: TryCatchViewModel
private lateinit var adapter: ApiUserAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycler_view)
setupUI()
setupViewModel()
setupObserver()
}
private fun setupUI() {
recyclerView.layoutManager = LinearLayoutManager(this)
adapter =
ApiUserAdapter(
arrayListOf()
)
recyclerView.addItemDecoration(
DividerItemDecoration(
recyclerView.context,
(recyclerView.layoutManager as LinearLayoutManager).orientation
)
)
recyclerView.adapter = adapter
}
private fun setupObserver() {
viewModel.getUsers().observe(this, {
when (it.status) {
Status.SUCCESS -> {
progressBar.visibility = View.GONE
it.data?.let { users -> renderList(users) }
recyclerView.visibility = View.VISIBLE
}
Status.LOADING -> {
progressBar.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
}
Status.ERROR -> {
//Handle Error
progressBar.visibility = View.GONE
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
}
}
})
}
private fun renderList(users: List<ApiUser>) {
adapter.addData(users)
adapter.notifyDataSetChanged()
}
private fun setupViewModel() {
viewModel = ViewModelProviders.of(
this,
ViewModelFactory(
ApiHelperImpl(RetrofitBuilder.apiService),
DatabaseHelperImpl(DatabaseBuilder.getInstance(applicationContext))
)
).get(TryCatchViewModel::class.java)
}
}
- 视图模型代码
package com.mindorks.example.coroutines.learn.errorhandling.trycatch
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mindorks.example.coroutines.data.api.ApiHelper
import com.mindorks.example.coroutines.data.local.DatabaseHelper
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.utils.Resource
import kotlinx.coroutines.launch
class TryCatchViewModel(
private val apiHelper: ApiHelper,
private val dbHelper: DatabaseHelper
) : ViewModel() {
private val users = MutableLiveData<Resource<List<ApiUser>>>()
init {
fetchUsers()
}
private fun fetchUsers() {
viewModelScope.launch {
users.postValue(Resource.loading(null))
try {
val usersFromApi = apiHelper.getUsers()
users.postValue(Resource.success(usersFromApi))
} catch (e: Exception) {
users.postValue(Resource.error("Something Went Wrong", null))
}
}
}
fun getUsers(): LiveData<Resource<List<ApiUser>>> {
return users
}
}
-
CoroutineExceptionHandler:了解如何使用 CoroutineExceptionHandler 处理 Kotlin 协程中的错误。
- 活动代码
package com.mindorks.example.coroutines.learn.errorhandling.exceptionhandler
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.mindorks.example.coroutines.R
import com.mindorks.example.coroutines.data.api.ApiHelperImpl
import com.mindorks.example.coroutines.data.api.RetrofitBuilder
import com.mindorks.example.coroutines.data.local.DatabaseBuilder
import com.mindorks.example.coroutines.data.local.DatabaseHelperImpl
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.learn.base.ApiUserAdapter
import com.mindorks.example.coroutines.utils.Status
import com.mindorks.example.coroutines.utils.ViewModelFactory
import kotlinx.android.synthetic.main.activity_recycler_view.*
class ExceptionHandlerActivity : AppCompatActivity() {
private lateinit var viewModel: ExceptionHandlerViewModel
private lateinit var adapter: ApiUserAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycler_view)
setupUI()
setupViewModel()
setupObserver()
}
private fun setupUI() {
recyclerView.layoutManager = LinearLayoutManager(this)
adapter =
ApiUserAdapter(
arrayListOf()
)
recyclerView.addItemDecoration(
DividerItemDecoration(
recyclerView.context,
(recyclerView.layoutManager as LinearLayoutManager).orientation
)
)
recyclerView.adapter = adapter
}
private fun setupObserver() {
viewModel.getUsers().observe(this, {
when (it.status) {
Status.SUCCESS -> {
progressBar.visibility = View.GONE
it.data?.let { users -> renderList(users) }
recyclerView.visibility = View.VISIBLE
}
Status.LOADING -> {
progressBar.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
}
Status.ERROR -> {
//Handle Error
progressBar.visibility = View.GONE
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
}
}
})
}
private fun renderList(users: List<ApiUser>) {
adapter.addData(users)
adapter.notifyDataSetChanged()
}
private fun setupViewModel() {
viewModel = ViewModelProviders.of(
this,
ViewModelFactory(
ApiHelperImpl(RetrofitBuilder.apiService),
DatabaseHelperImpl(DatabaseBuilder.getInstance(applicationContext))
)
).get(ExceptionHandlerViewModel::class.java)
}
}
- 视图模型代码
package com.mindorks.example.coroutines.learn.errorhandling.exceptionhandler
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mindorks.example.coroutines.data.api.ApiHelper
import com.mindorks.example.coroutines.data.local.DatabaseHelper
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.utils.Resource
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.launch
class ExceptionHandlerViewModel(
private val apiHelper: ApiHelper,
private val dbHelper: DatabaseHelper
) : ViewModel() {
private val users = MutableLiveData<Resource<List<ApiUser>>>()
private val exceptionHandler = CoroutineExceptionHandler { _, exception ->
users.postValue(Resource.error("Something Went Wrong", null))
}
init {
fetchUsers()
}
private fun fetchUsers() {
viewModelScope.launch(exceptionHandler) {
users.postValue(Resource.loading(null))
val usersFromApi = apiHelper.getUsers()
users.postValue(Resource.success(usersFromApi))
}
}
fun getUsers(): LiveData<Resource<List<ApiUser>>> {
return users
}
}
-
忽略错误并继续:了解如何使用
supervisorScope
忽略任务的错误并继续执行其他任务。换句话说,如果有两个以上的子作业在一个主管下并行运行,一个子作业失败了,我们可以继续另一个。- 活动代码
package com.mindorks.example.coroutines.learn.errorhandling.supervisor
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.mindorks.example.coroutines.R
import com.mindorks.example.coroutines.data.api.ApiHelperImpl
import com.mindorks.example.coroutines.data.api.RetrofitBuilder
import com.mindorks.example.coroutines.data.local.DatabaseBuilder
import com.mindorks.example.coroutines.data.local.DatabaseHelperImpl
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.learn.base.ApiUserAdapter
import com.mindorks.example.coroutines.utils.Status
import com.mindorks.example.coroutines.utils.ViewModelFactory
import kotlinx.android.synthetic.main.activity_recycler_view.*
class IgnoreErrorAndContinueActivity : AppCompatActivity() {
private lateinit var viewModel: IgnoreErrorAndContinueViewModel
private lateinit var adapter: ApiUserAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycler_view)
setupUI()
setupViewModel()
setupObserver()
}
private fun setupUI() {
recyclerView.layoutManager = LinearLayoutManager(this)
adapter =
ApiUserAdapter(
arrayListOf()
)
recyclerView.addItemDecoration(
DividerItemDecoration(
recyclerView.context,
(recyclerView.layoutManager as LinearLayoutManager).orientation
)
)
recyclerView.adapter = adapter
}
private fun setupObserver() {
viewModel.getUsers().observe(this, {
when (it.status) {
Status.SUCCESS -> {
progressBar.visibility = View.GONE
it.data?.let { users -> renderList(users) }
recyclerView.visibility = View.VISIBLE
}
Status.LOADING -> {
progressBar.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
}
Status.ERROR -> {
//Handle Error
progressBar.visibility = View.GONE
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
}
}
})
}
private fun renderList(users: List<ApiUser>) {
adapter.addData(users)
adapter.notifyDataSetChanged()
}
private fun setupViewModel() {
viewModel = ViewModelProviders.of(
this,
ViewModelFactory(
ApiHelperImpl(RetrofitBuilder.apiService),
DatabaseHelperImpl(DatabaseBuilder.getInstance(applicationContext))
)
).get(IgnoreErrorAndContinueViewModel::class.java)
}
}
- 视图模型代码
package com.mindorks.example.coroutines.learn.errorhandling.supervisor
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mindorks.example.coroutines.data.api.ApiHelper
import com.mindorks.example.coroutines.data.local.DatabaseHelper
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.utils.Resource
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
class IgnoreErrorAndContinueViewModel(
private val apiHelper: ApiHelper,
private val dbHelper: DatabaseHelper
) : ViewModel() {
private val users = MutableLiveData<Resource<List<ApiUser>>>()
init {
fetchUsers()
}
private fun fetchUsers() {
viewModelScope.launch {
users.postValue(Resource.loading(null))
try {
// supervisorScope is needed, so that we can ignore error and continue
// here, more than two child jobs are running in parallel under a supervisor, one child job gets failed, we can continue with other.
supervisorScope {
val usersFromApiDeferred = async { apiHelper.getUsersWithError() }
val moreUsersFromApiDeferred = async { apiHelper.getMoreUsers() }
val usersFromApi = try {
usersFromApiDeferred.await()
} catch (e: Exception) {
emptyList()
}
val moreUsersFromApi = try {
moreUsersFromApiDeferred.await()
} catch (e: Exception) {
emptyList()
}
val allUsersFromApi = mutableListOf<ApiUser>()
allUsersFromApi.addAll(usersFromApi)
allUsersFromApi.addAll(moreUsersFromApi)
users.postValue(Resource.success(allUsersFromApi))
}
} catch (e: Exception) {
users.postValue(Resource.error("Something Went Wrong", null))
}
}
}
fun getUsers(): LiveData<Resource<List<ApiUser>>> {
return users
}
}
-
单元测试:了解如何为使用 Kotlin 协程的 ViewModel 编写单元测试。
- 查看模型测试代码
package com.mindorks.example.coroutines.learn.retrofit.single
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import com.mindorks.example.coroutines.data.api.ApiHelper
import com.mindorks.example.coroutines.data.local.DatabaseHelper
import com.mindorks.example.coroutines.data.model.ApiUser
import com.mindorks.example.coroutines.utils.Resource
import com.mindorks.example.coroutines.utils.TestCoroutineRule
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.*
import org.mockito.junit.MockitoJUnitRunner
@ExperimentalCoroutinesApi
@RunWith(MockitoJUnitRunner::class)
class SingleNetworkCallViewModelTest {
@get:Rule
val testInstantTaskExecutorRule: TestRule = InstantTaskExecutorRule()
@get:Rule
val testCoroutineRule = TestCoroutineRule()
@Mock
private lateinit var apiHelper: ApiHelper
@Mock
private lateinit var databaseHelper: DatabaseHelper
@Mock
private lateinit var apiUsersObserver: Observer<Resource<List<ApiUser>>>
@Before
fun setUp() {
// do something if required
}
@Test
fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
testCoroutineRule.runBlockingTest {
doReturn(emptyList<ApiUser>())
.`when`(apiHelper)
.getUsers()
val viewModel = SingleNetworkCallViewModel(apiHelper, databaseHelper)
viewModel.getUsers().observeForever(apiUsersObserver)
verify(apiHelper).getUsers()
verify(apiUsersObserver).onChanged(Resource.success(emptyList()))
viewModel.getUsers().removeObserver(apiUsersObserver)
}
}
@Test
fun givenServerResponseError_whenFetch_shouldReturnError() {
testCoroutineRule.runBlockingTest {
val errorMessage = "Error Message For You"
doThrow(RuntimeException(errorMessage))
.`when`(apiHelper)
.getUsers()
val viewModel = SingleNetworkCallViewModel(apiHelper, databaseHelper)
viewModel.getUsers().observeForever(apiUsersObserver)
verify(apiHelper).getUsers()
verify(apiUsersObserver).onChanged(
Resource.error(
RuntimeException(errorMessage).toString(),
null
)
)
viewModel.getUsers().removeObserver(apiUsersObserver)
}
}
@After
fun tearDown() {
// do something if required
}
}
从这个项目学习 Kotlin Coroutines for Android 时有用的参考资料
- 在 Android 中掌握 Kotlin 协程 - 分步指南
- Kotlin 协程中的挂起函数是什么?
- 使用 Kotlin 协程并行多个网络调用
- 使用 ViewModelScope 来减少带有协程的样板代码
- Kotlin 协程中的启动与异步
- Kotlin withContext vs Async-await
- Android 中带有 Kotlin 协程的房间数据库
- 使用 Kotlin 协程和 LiveData 对 ViewModel 进行单元测试
- Kotlin 协程中的异常处理
- Mockito 无法模拟,因为:Kotlin 中的最后一堂课
作者:amitshekhariitbhu
链接:https://github.com/MindorksOpenSource/Kotlin-Coroutines-Android-Examples