如何取消 运行 LiveData 协程块
How to cancel a running LiveData Coroutine Block
通过使用 LiveData 的最新版本 "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha03",我在 ViewModel 中使用 LiveData 的新构建块(LiveData + Coroutine)开发了一个名为 "Search Products" 的功能的代码,该功能使用 LiveData + Coroutine 执行同步网络调用相应地改造和更新 ViewModel 中的不同标志(isLoading、isError)。我在 "query" LiveData 上使用 Transforamtions.switchMap,所以每当 "query" 从 UI 发生变化时,"Search Products" 代码开始使用 [=21= 执行].一切正常,只是我想在 "query" LiveData 发生变化时取消之前的 Retrofit Call。目前我看不出有什么办法可以做到这一点。任何帮助,将不胜感激。
class ProductSearchViewModel : ViewModel() {
val completableJob = Job()
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)
// Query Observable Field
val query: MutableLiveData<String> = MutableLiveData()
// IsLoading Observable Field
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
val products: LiveData<List<ProductModel>> = query.switchMap { q ->
liveData(context = coroutineScope.coroutineContext) {
emit(emptyList())
_isLoading.postValue(true)
val service = MyApplication.getRetrofitService()
val response = service?.searchProducts(q)
if (response != null && response.isSuccessful && response.body() != null) {
_isLoading.postValue(false)
val body = response.body()
if (body != null && body.results != null) {
emit(body.results)
}
} else {
_isLoading.postValue(false)
}
}
}
}
取消父作用域时应取消改造请求。
class ProductSearchViewModel : ViewModel() {
val completableJob = Job()
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)
/**
* Adding job that will be used to cancel liveData builder.
* Be wary - after cancelling, it'll return a new one like:
*
* ongoingRequestJob.cancel() // Cancelled
* ongoingRequestJob.isActive // Will return true because getter created a new one
*/
var ongoingRequestJob = Job(coroutineScope.coroutineContext[Job])
get() = if (field.isActive) field else Job(coroutineScope.coroutineContext[Job])
// Query Observable Field
val query: MutableLiveData<String> = MutableLiveData()
// IsLoading Observable Field
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
val products: LiveData<List<ProductModel>> = query.switchMap { q ->
liveData(context = ongoingRequestJob) {
emit(emptyList())
_isLoading.postValue(true)
val service = MyApplication.getRetrofitService()
val response = service?.searchProducts(q)
if (response != null && response.isSuccessful && response.body() != null) {
_isLoading.postValue(false)
val body = response.body()
if (body != null && body.results != null) {
emit(body.results)
}
} else {
_isLoading.postValue(false)
}
}
}
}
然后你需要在需要的时候取消ongoingRequestJob
。下次 liveData(context = ongoingRequestJob)
被触发时,因为它会 return 一个新工作,它应该 运行 没有问题。您只需要在需要的地方取消它,即在 query.switchMap
函数范围内。
您可以通过两种方式解决这个问题:
方法#1(简易方法)
就像 Mel 在他的 中解释的那样,您可以在 switchMap 之外保留对作业实例的引用,并在返回 switchMap 中的新 liveData 之前取消该作业的实例。
class ProductSearchViewModel : ViewModel() {
// Job instance
private var job = Job()
val products = Transformations.switchMap(_query) {
job.cancel() // Cancel this job instance before returning liveData for new query
job = Job() // Create new one and assign to that same variable
// Pass that instance to CoroutineScope so that it can be cancelled for next query
liveData(CoroutineScope(job + Dispatchers.IO).coroutineContext) {
// Your code here
}
}
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
方法 # 2(不太干净,但独立且可重复使用)
由于 liveData {}
构建器块 运行s 在协程范围内,您可以结合使用 CompletableDeffered
和协程 launch
构建器来暂停该 liveData 块并观察query
liveData 手动启动网络请求作业。
class ProductSearchViewModel : ViewModel() {
private val _query = MutableLiveData<String>()
val products: LiveData<List<String>> = liveData {
var job: Job? = null // Job instance to keep reference of last job
// LiveData observer for query
val queryObserver = Observer<String> {
job?.cancel() // Cancel job before launching new coroutine
job = GlobalScope.launch {
// Your code here
}
}
// Observe query liveData here manually
_query.observeForever(queryObserver)
try {
// Create CompletableDeffered instance and call await.
// Calling await will suspend this current block
// from executing anything further from here
CompletableDeferred<Unit>().await()
} finally {
// Since we have called await on CompletableDeffered above,
// this will cause an Exception on this liveData when onDestory
// event is called on a lifeCycle . By wrapping it in
// try/finally we can use this to know when that will happen and
// cleanup to avoid any leaks.
job?.cancel()
_query.removeObserver(queryObserver)
}
}
}
您可以在demo project
中下载并测试运行这两种方法
编辑:更新方法 # 1 以在 onCleared 方法上添加作业取消,正如 yasir 在评论中指出的那样。
通过使用 LiveData 的最新版本 "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha03",我在 ViewModel 中使用 LiveData 的新构建块(LiveData + Coroutine)开发了一个名为 "Search Products" 的功能的代码,该功能使用 LiveData + Coroutine 执行同步网络调用相应地改造和更新 ViewModel 中的不同标志(isLoading、isError)。我在 "query" LiveData 上使用 Transforamtions.switchMap,所以每当 "query" 从 UI 发生变化时,"Search Products" 代码开始使用 [=21= 执行].一切正常,只是我想在 "query" LiveData 发生变化时取消之前的 Retrofit Call。目前我看不出有什么办法可以做到这一点。任何帮助,将不胜感激。
class ProductSearchViewModel : ViewModel() {
val completableJob = Job()
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)
// Query Observable Field
val query: MutableLiveData<String> = MutableLiveData()
// IsLoading Observable Field
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
val products: LiveData<List<ProductModel>> = query.switchMap { q ->
liveData(context = coroutineScope.coroutineContext) {
emit(emptyList())
_isLoading.postValue(true)
val service = MyApplication.getRetrofitService()
val response = service?.searchProducts(q)
if (response != null && response.isSuccessful && response.body() != null) {
_isLoading.postValue(false)
val body = response.body()
if (body != null && body.results != null) {
emit(body.results)
}
} else {
_isLoading.postValue(false)
}
}
}
}
取消父作用域时应取消改造请求。
class ProductSearchViewModel : ViewModel() {
val completableJob = Job()
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)
/**
* Adding job that will be used to cancel liveData builder.
* Be wary - after cancelling, it'll return a new one like:
*
* ongoingRequestJob.cancel() // Cancelled
* ongoingRequestJob.isActive // Will return true because getter created a new one
*/
var ongoingRequestJob = Job(coroutineScope.coroutineContext[Job])
get() = if (field.isActive) field else Job(coroutineScope.coroutineContext[Job])
// Query Observable Field
val query: MutableLiveData<String> = MutableLiveData()
// IsLoading Observable Field
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
val products: LiveData<List<ProductModel>> = query.switchMap { q ->
liveData(context = ongoingRequestJob) {
emit(emptyList())
_isLoading.postValue(true)
val service = MyApplication.getRetrofitService()
val response = service?.searchProducts(q)
if (response != null && response.isSuccessful && response.body() != null) {
_isLoading.postValue(false)
val body = response.body()
if (body != null && body.results != null) {
emit(body.results)
}
} else {
_isLoading.postValue(false)
}
}
}
}
然后你需要在需要的时候取消ongoingRequestJob
。下次 liveData(context = ongoingRequestJob)
被触发时,因为它会 return 一个新工作,它应该 运行 没有问题。您只需要在需要的地方取消它,即在 query.switchMap
函数范围内。
您可以通过两种方式解决这个问题:
方法#1(简易方法)
就像 Mel 在他的
class ProductSearchViewModel : ViewModel() {
// Job instance
private var job = Job()
val products = Transformations.switchMap(_query) {
job.cancel() // Cancel this job instance before returning liveData for new query
job = Job() // Create new one and assign to that same variable
// Pass that instance to CoroutineScope so that it can be cancelled for next query
liveData(CoroutineScope(job + Dispatchers.IO).coroutineContext) {
// Your code here
}
}
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
方法 # 2(不太干净,但独立且可重复使用)
由于 liveData {}
构建器块 运行s 在协程范围内,您可以结合使用 CompletableDeffered
和协程 launch
构建器来暂停该 liveData 块并观察query
liveData 手动启动网络请求作业。
class ProductSearchViewModel : ViewModel() {
private val _query = MutableLiveData<String>()
val products: LiveData<List<String>> = liveData {
var job: Job? = null // Job instance to keep reference of last job
// LiveData observer for query
val queryObserver = Observer<String> {
job?.cancel() // Cancel job before launching new coroutine
job = GlobalScope.launch {
// Your code here
}
}
// Observe query liveData here manually
_query.observeForever(queryObserver)
try {
// Create CompletableDeffered instance and call await.
// Calling await will suspend this current block
// from executing anything further from here
CompletableDeferred<Unit>().await()
} finally {
// Since we have called await on CompletableDeffered above,
// this will cause an Exception on this liveData when onDestory
// event is called on a lifeCycle . By wrapping it in
// try/finally we can use this to know when that will happen and
// cleanup to avoid any leaks.
job?.cancel()
_query.removeObserver(queryObserver)
}
}
}
您可以在demo project
中下载并测试运行这两种方法编辑:更新方法 # 1 以在 onCleared 方法上添加作业取消,正如 yasir 在评论中指出的那样。