如何将viewModelScope中的异步结果传递至Activity及Compose页面?
ViewModelScope请求结果传递到Composable的问题解决
我尝试把viewModelScope中API请求的结果传递到Composable里,写了如下的CurrencyConverterViewModel:
class CurrencyConverterViewModel : ViewModel() { lateinit var response: CurrencyCodesModel var listOfNames : List<String> = emptyList() init { viewModelScope.launch { getListOfCurrencyNamesApi() } } suspend fun getCurrencyCodes() { response = CurrencyConverterInstance .currencyApiService .getCurrencyCodes() } private suspend fun getListOfCurrencyNamesApi() { getCurrencyCodes() listOfNames = response.supported_codes.map { it[1] } } }
其中getCurrencyCodes负责调用API并存储返回结果,getListOfCurrencyNamesApi从返回结果中提取数据转换成字符串列表。
但在Composable中创建ViewModel实例后,尽管Logcat显示listOfNames已有数据,把它赋值给currencyListOfNames.value时却始终是空的:
@Composable fun CurrencyConverter() { val myViewModel: CurrencyConverterViewModel = viewModel() val currencyListOfNames = remember { mutableStateOf<List<String>>(emptyList()) } currencyListOfNames.value = myViewModel.listOfNames }
问题原因
- 首次Composable组合时,ViewModel里的协程还在执行API请求,
listOfNames还是初始的空列表,此时赋值自然拿不到数据。 - 协程执行完更新
listOfNames后,因为它只是普通变量,Composable无法感知到数据变化,不会触发重组去更新currencyListOfNames。
解决方案:使用可观察状态容器
要让Composable能感知到ViewModel中数据的变化,需要用StateFlow或LiveData这类可观察的状态容器。
方案一:StateFlow(推荐用于Jetpack Compose)
修改ViewModel,把listOfNames改成MutableStateFlow:
class CurrencyConverterViewModel : ViewModel() { lateinit var response: CurrencyCodesModel // 内部用MutableStateFlow存储数据,初始为空列表 private val _listOfNames = MutableStateFlow<List<String>>(emptyList()) // 对外暴露不可变的StateFlow,避免外部直接修改数据 val listOfNames = _listOfNames.asStateFlow() init { viewModelScope.launch { getListOfCurrencyNamesApi() } } suspend fun getCurrencyCodes() { response = CurrencyConverterInstance .currencyApiService .getCurrencyCodes() } private suspend fun getListOfCurrencyNamesApi() { getCurrencyCodes() // 更新StateFlow的值,自动通知订阅者 _listOfNames.value = response.supported_codes.map { it[1] } } }
在Composable中用collectAsState订阅StateFlow,数据变化时自动触发重组:
@Composable fun CurrencyConverter() { val myViewModel: CurrencyConverterViewModel = viewModel() // 自动收集StateFlow的数据,默认值为空列表 val currencyListOfNames by myViewModel.listOfNames.collectAsState() // 示例:用LazyColumn展示列表 LazyColumn { items(currencyListOfNames) { currencyName -> Text( text = currencyName, modifier = Modifier.padding(16.dp) ) } } }
方案二:LiveData(如果习惯使用LiveData)
修改ViewModel使用LiveData:
class CurrencyConverterViewModel : ViewModel() { lateinit var response: CurrencyCodesModel private val _listOfNames = MutableLiveData<List<String>>(emptyList()) val listOfNames: LiveData<List<String>> = _listOfNames init { viewModelScope.launch { getListOfCurrencyNamesApi() } } suspend fun getCurrencyCodes() { response = CurrencyConverterInstance .currencyApiService .getCurrencyCodes() } private suspend fun getListOfCurrencyNamesApi() { getCurrencyCodes() _listOfNames.value = response.supported_codes.map { it[1] } } }
在Composable中用observeAsState观察LiveData:
@Composable fun CurrencyConverter() { val myViewModel: CurrencyConverterViewModel = viewModel() // 将LiveData转换为Compose状态,默认值为空列表 val currencyListOfNames by myViewModel.listOfNames.observeAsState(emptyList()) // 展示列表示例 LazyColumn { items(currencyListOfNames) { currencyName -> Text( text = currencyName, modifier = Modifier.padding(16.dp) ) } } }
内容的提问来源于stack exchange,提问作者Sebastian Litwiniuk
相关产品推荐
相关产品推荐

