如何在 ViewModel 中编写 StateFlow 而不是在 Compose 中编写 produceState?
How to write a StateFlow in ViewModel instead of produceState in Compose?
代码A来自官方样本project。
我正在通过 article 和项目学习 Compose。
文章告诉我以下内容。
我们可以通过使用数据流,DetailsUiState 类型的 StateFlow,ViewModel 在信息准备就绪时更新,ViewModel 层中映射屏幕需要显示的内容和 UiState使用您已经了解的 collectAsState() API 组合收集。
您能否将代码 A 更改为 ViewModel 中的 StateFlow 以完成相同的工作?
代码A
data class DetailsUiState(
val cityDetails: ExploreModel? = null,
val isLoading: Boolean = false,
val throwError: Boolean = false
)
@Composable
fun DetailsScreen(
onErrorLoading: () -> Unit,
modifier: Modifier = Modifier,
viewModel: DetailsViewModel = viewModel()
) {
val uiState by produceState(initialValue = DetailsUiState(isLoading = true)) {
val cityDetailsResult = viewModel.cityDetails
value = if (cityDetailsResult is Result.Success<ExploreModel>) {
DetailsUiState(cityDetailsResult.data)
} else {
DetailsUiState(throwError = true)
}
}
when {
uiState.cityDetails != null -> {
DetailsContent(uiState.cityDetails!!, modifier.fillMaxSize())
}
uiState.isLoading -> {
...
}
else -> { onErrorLoading() }
}
}
@HiltViewModel
class DetailsViewModel @Inject constructor(
private val destinationsRepository: DestinationsRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
private val cityName = savedStateHandle.get<String>(KEY_ARG_DETAILS_CITY_NAME)!!
val cityDetails: Result<ExploreModel>
get() {
val destination = destinationsRepository.getDestination(cityName)
return if (destination != null) {
Result.Success(destination)
} else {
Result.Error(IllegalArgumentException("City doesn't exist"))
}
}
}
嘿,这是我对你问题的回答
@Composable
fun DetailsScreen(
onErrorLoading: () -> Unit,
modifier: Modifier = Modifier,
viewModel: DetailsViewModel
) {
val cityDetails by viewModel.cityDetails.collectAsState()
val error by viewModel.error.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
// Retrieve cityDetails on first composition
LaunchedEffect(Unit) {
viewModel.getCityDetails()
}
// If is loading, do whatever you want
if (isLoading) {
...
}
// Check is there is an error
error
?.let {
onErrorLoading()
}
// If there is no error check if you have some cityDetails
?: cityDetails?.let {
// if you have some cityDetails draw composable
DetailsContent(it, modifier.fillMaxSize())
}
}
@HiltViewModel
class DetailsViewModel @Inject constructor(
private val destinationsRepository: DestinationsRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
private val cityName = savedStateHandle.get<String>(KEY_ARG_DETAILS_CITY_NAME)!!
val cityDetails = MutableStateFlow<ExploreModel?>(null)
val error = MutableStateFlow<Throwable?>(null)
val isLoading = MutableStateFlow(false)
fun getCityDetails() = viewModelScope.launch {
isLoading.emit(true)
val destination = destinationsRepository.getDestination(cityName)
if (destination != null) {
cityDetails.emit(destination)
} else {
error.emit(IllegalArgumentException("City doesn't exist"))
}
isLoading.emit(false)
}
}
produceState 与使用 LaunchedEffect
的包装器没有什么不同,它存储初始状态,当 Composable 在组合时组合,然后每次使用协程从 ProduceStateScopeImpl
得到结果 returns。
@Composable
fun <T> produceState(
initialValue: T,
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
): State<T> {
val result = remember { mutableStateOf(initialValue) }
LaunchedEffect(Unit) {
ProduceStateScopeImpl(result, coroutineContext).producer()
}
return result
}
因此,您可以将其替换为具有 val result = remember { mutableStateOf(initialValue) }
的任何内容,并像在另一个可组合项中的 ViewModel 或图像加载状态中一样更新此结果。您可以在您的代码中更改它,效果将相同。
代码A来自官方样本project。
我正在通过 article 和项目学习 Compose。
文章告诉我以下内容。
我们可以通过使用数据流,DetailsUiState 类型的 StateFlow,ViewModel 在信息准备就绪时更新,ViewModel 层中映射屏幕需要显示的内容和 UiState使用您已经了解的 collectAsState() API 组合收集。
您能否将代码 A 更改为 ViewModel 中的 StateFlow 以完成相同的工作?
代码A
data class DetailsUiState(
val cityDetails: ExploreModel? = null,
val isLoading: Boolean = false,
val throwError: Boolean = false
)
@Composable
fun DetailsScreen(
onErrorLoading: () -> Unit,
modifier: Modifier = Modifier,
viewModel: DetailsViewModel = viewModel()
) {
val uiState by produceState(initialValue = DetailsUiState(isLoading = true)) {
val cityDetailsResult = viewModel.cityDetails
value = if (cityDetailsResult is Result.Success<ExploreModel>) {
DetailsUiState(cityDetailsResult.data)
} else {
DetailsUiState(throwError = true)
}
}
when {
uiState.cityDetails != null -> {
DetailsContent(uiState.cityDetails!!, modifier.fillMaxSize())
}
uiState.isLoading -> {
...
}
else -> { onErrorLoading() }
}
}
@HiltViewModel
class DetailsViewModel @Inject constructor(
private val destinationsRepository: DestinationsRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
private val cityName = savedStateHandle.get<String>(KEY_ARG_DETAILS_CITY_NAME)!!
val cityDetails: Result<ExploreModel>
get() {
val destination = destinationsRepository.getDestination(cityName)
return if (destination != null) {
Result.Success(destination)
} else {
Result.Error(IllegalArgumentException("City doesn't exist"))
}
}
}
嘿,这是我对你问题的回答
@Composable
fun DetailsScreen(
onErrorLoading: () -> Unit,
modifier: Modifier = Modifier,
viewModel: DetailsViewModel
) {
val cityDetails by viewModel.cityDetails.collectAsState()
val error by viewModel.error.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
// Retrieve cityDetails on first composition
LaunchedEffect(Unit) {
viewModel.getCityDetails()
}
// If is loading, do whatever you want
if (isLoading) {
...
}
// Check is there is an error
error
?.let {
onErrorLoading()
}
// If there is no error check if you have some cityDetails
?: cityDetails?.let {
// if you have some cityDetails draw composable
DetailsContent(it, modifier.fillMaxSize())
}
}
@HiltViewModel
class DetailsViewModel @Inject constructor(
private val destinationsRepository: DestinationsRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
private val cityName = savedStateHandle.get<String>(KEY_ARG_DETAILS_CITY_NAME)!!
val cityDetails = MutableStateFlow<ExploreModel?>(null)
val error = MutableStateFlow<Throwable?>(null)
val isLoading = MutableStateFlow(false)
fun getCityDetails() = viewModelScope.launch {
isLoading.emit(true)
val destination = destinationsRepository.getDestination(cityName)
if (destination != null) {
cityDetails.emit(destination)
} else {
error.emit(IllegalArgumentException("City doesn't exist"))
}
isLoading.emit(false)
}
}
produceState 与使用 LaunchedEffect
的包装器没有什么不同,它存储初始状态,当 Composable 在组合时组合,然后每次使用协程从 ProduceStateScopeImpl
得到结果 returns。
@Composable
fun <T> produceState(
initialValue: T,
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
): State<T> {
val result = remember { mutableStateOf(initialValue) }
LaunchedEffect(Unit) {
ProduceStateScopeImpl(result, coroutineContext).producer()
}
return result
}
因此,您可以将其替换为具有 val result = remember { mutableStateOf(initialValue) }
的任何内容,并像在另一个可组合项中的 ViewModel 或图像加载状态中一样更新此结果。您可以在您的代码中更改它,效果将相同。