如何在 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 或图像加载状态中一样更新此结果。您可以在您的代码中更改它,效果将相同。