我有以下内容:
interface CartRepository {
fun getCart(): Flow<CartState>
}
interface ProductRepository {
fun getProductByEan(ean: String): Flow<Either<ServerError, Product?>>
}
class ScanViewModel(
private val productRepository: ProductRepository,
private val cartRepository: CartRepository
) :
BaseViewModel<ScanUiState>(Initial) {
fun fetchProduct(ean: String) = viewModelScope.launch {
setState(Loading)
productRepository
.getProductByEan(ean)
.combine(cartRepository.getCart(), combineToGridItem())
.collect { result ->
when (result) {
is Either.Left -> {
sendEvent(Error(R.string.error_barcode_product_not_found, null))
setState(Initial)
}
is Either.Right -> {
setState(ProductUpdated(result.right))
}
}
}
}
}
当用户扫描条形码时,fetchProduct
正在被调用。每次一个新的协程被建立。过了一段时间,有很多在后台运行,当所有的购物车状态更新时,combine
被触发,这可能会导致错误。
我想取消所有旧的协程,只运行最新的呼叫并更新购物车更改。
我知道我可以通过保存作业并在开始新作业之前取消它来执行以下操作。但这真的是正确的选择吗?好像我错过了什么。
var searchJob: Job? = null
private fun processImage(frame: Frame) {
barcodeScanner.process(frame.toInputImage(this))
.addOnSuccessListener { barcodes ->
barcodes.firstOrNull()?.rawValue?.let { ean ->
searchJob?.cancel()
searchJob = viewModel.fetchProduct(ean)
}
}
.addOnFailureListener {
Timber.e(it)
messageMaker.showError(
binding.root,
getString(R.string.unknown_error)
)
}
}
我也可以在我的ViewModel中有一个MutableSharedFlow,以确保UI只对用户最近一次获取的产品做出反应:
private val productFlow = MutableSharedFlow<Either<ServerError, Product?>>(replay = 1)
init {
viewModelScope.launch {
productFlow.combine(
mycroftRepository.getCart(),
combineToGridItem()
).collect { result ->
when (result) {
is Either.Right -> {
setState(ProductUpdated(result.right))
}
else -> {
sendEvent(Error(R.string.error_barcode_product_not_found, null))
setState(Initial)
}
}
}
}
}
fun fetchProduct(ean: String) = viewModelScope.launch {
setState(Loading)
repository.getProductByEan(ean).collect { result ->
productFlow.emit(result)
}
}
处理这种情况的最佳实践是什么?
我想不出一个更简单的模式来在开始一个新任务时取消任何以前的任务。
如果您担心在屏幕旋转时丢失存储的作业引用(您可能不会,因为Fragment实例通常在旋转时重用),您可以将作业存储和取消移动到ViewModel中:
private var fetchProductJob: Job? = null
fun fetchProduct(ean: String) {
fetchProductJob?.cancel()
fetchProductJob = viewModelScope.launch {
//...
}
}
如果您重复使用此模式,您可以像这样创建一个helper类。不知道有没有更好的办法
class SingleJobPipe(val scope: CoroutineScope) {
private var job: Job? = null
fun launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job = synchronized(this) {
job?.cancel()
scope.launch(context, start, block).also { job = it }
}
}
// ...
private val fetchProductPipe = SingleJobPipe(viewModelScope)
fun fetchProduct(ean: String) = fetchProductPipe.launch {
//...
}