如何在后台执行耗时的操作,并在Jetpack Compose的Android开发中使用Kotlin中的协程更新UI.&l



我正在开发一个android应用程序,它是一个使用Jetpack Compose的比特币钱包。

我有Wallet.kt文件:

fun sync() {
Log.i(TAG, "Wallet is syncing")
wallet.sync(blockchain, LogProgress)
}
fun getBalance(): ULong = wallet.getBalance().total

然后在主屏幕。kt有

internal class WalletViewModel() : ViewModel() {
private var _balance: MutableLiveData<ULong> = MutableLiveData(0u)
val balance: LiveData<ULong>
get() = _balance
fun updateBalance() {
Wallet.sync()
_balance.value = Wallet.getBalance()
}

然后在此之外是可组合函数homesscreen

internal fun HomeScreen(
navController: NavController,
walletViewModel: WalletViewModel = viewModel()
) {
val balance by walletViewModel.balance.observeAsState()
Image(Modifier.clickable{ walletViewModel.updateBalance() }
}

我的问题是当我点击可点击的图像时,整个应用程序冻结,直到updateBalance()完成。我了解到这是因为sync()功能内部钱包。kt文件在主线程上执行网络任务,应用程序在主线程中,所以整个应用程序必须等待,直到同步完成。

你能建议我应该如何实现协同程序或不同的方式,使同步发生在后台线程,然后更新_balance到/在Main ?

我尝试了很多事情,包括在viewModelScope中的sync()和async之前暂停,但似乎没有什么是我想要的。

感谢

您可以通过两种方式解决它,一种是将异步调用附加到viewModelScope并将异步调用标记为挂起,另一种是在您的存储库中创建具有上下文的协程并在另一个线程中执行它。

解决方案1

fun updateBalance() {
viewModelScope.launch {
Wallet.sync()
_balance.value = Wallet.getBalance()
}
}
suspend fun sync() {
Log.i(TAG, "Wallet is syncing")
wallet.sync(blockchain, LogProgress)
}

解决方案2

suspend fun sync() {
Log.i(TAG, "Wallet is syncing")
withContext(Dispatcher.IO) {
wallet.sync(blockchain, LogProgress)
}
}

正确的解决方案是:

fun updateBalance() {
viewModelScope.launch(Dispatchers.IO){
Wallet.sync()

withContext(Dispatchers.Main){
_balance.value = Wallet.getBalance()
}
}
}

在后台线程同步钱包,完成后更新UI。

我个人会通过移动到线程安全的StateFlow来解决这个问题,而不是总是需要在主线程上发布的LiveData。也就是说,解决方案应该非常简单:

fun updateBalance() {
viewModelScope(Dispatcher.IO).launch {
wallet.sync(blockchain, LogProgress)
_balance.postValue(Wallet.getBalance())
}
}

使用状态流,它甚至更简单:

fun updateBalance() {
viewModelScope(Dispatcher.IO).launch {
wallet.sync(blockchain, LogProgress)
_balance.value = Wallet.getBalance()
}
}

你必须使用collectAsStateWithLifecycle将StateFlow转换为一个Compose State对象。