我正在尝试通过 Codelab 学习 kotlin,最近刚刚了解了 viewModel、uiState、UDF 等的使用。 (刚刚完成了乱码代码实验室)作为代码实验室之间的间歇,我涉足了我想为自己创建的应用程序,作为应用我所学到的知识的一种方式,即使很明显我无法完成它,直到我会学到更多东西。尽管如此,一旦我学到了新的东西,我经常会尝试将其付诸实践,作为在 Codelab 之外更熟悉 kotlin 的一种方式。 上次我尝试为我的应用程序创建一个 ViewModel-UiState-UiScreen 结构,该结构之前只是单个 MainActivity 文件中的一堆可组合项。
因为我还是一个初学者,所以我尝试了自己笨拙的方法,通过使用状态字符串变量和 when 条件来调用不同的组合来制作具有可组合项的多屏幕 Ui。在引入 VievModel 之前,单击 UI 中的按钮只需为状态字符串变量分配与新“屏幕”相对应的值;现在的想法是做几乎相同的事情,通过调用 VievModel 的处理函数并最终向它们传递必要的参数,以便它们可以更新数据,更改作为 uiState 一部分的相同状态变量,最后更新uiState 因此理想地触发 UI 的重组,就像以前发生的那样。那不会发生。
通过调试和日志警报,我设法验证 UI 是否正确启动,当条件启动第一个“屏幕”时,按钮调用 VieWmodel 处理函数并且数据更新为正确的值,但重组不会触发:我可以按下按钮并看到多条日志消息,确认数据和状态流已更新,但 When 循环永远不会重新启动,并且屏幕保持不变。 我实际上没有必要为此获得一个工作代码,因为当我经历更多的代码实验室时,我必须多次更改它,但我担心到目前为止我误解了一些重要的东西,这让我很烦恼。我只是想了解我错过了整件事。
// UI SCREEN: the main composable supposed to manage my screens:
@Composable
fun BigBrother(gViewModel: GViewModel = viewModel())
{
val gUiState by gViewModel.uiState.collectAsState()
Log.d(TAG, "Chosen path is ${gUiState.direction}")
when(gUiState.direction) {
"START" -> StartScreen()
"DISPLAY" -> Header(item=gUiState.item)
"EDITHEADER" -> EditHeader(item=gUiState.item)
"CHANGER" -> Changer(parameter = gUiState.parameterChange)
"CHOOSER" -> Chooser(modifier = Modifier)
"GALLERY" -> ScrollItems(catalogue = gUiState.catalogItem, modifier = Modifier)
else -> StartScreen()
}
}
@Composable
fun StartScreen( modifier: Modifier = Modifier) {
Log.d(TAG, "Launching START")
Column(modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
Text(
text = stringResource(R.string.appname_txt)
)
Button(
onClick = {GViewModel().redirection("CHOOSER")
Log.d(TAG, "Button1 clicked, launching CHOOSER")},
modifier = Modifier,
) {
Text(
text = "New",
)
}
Button(
onClick = {GViewModel().redirection("GALLERY")
Log.d(TAG, "Button2 clicked, launching GALLERY")},
modifier = Modifier,
) {
Text(
text = "Browse",
modifier = Modifier
)
}
}
}
// uiState:
data class GUiState(
val item: Item = Item(....),
val catalogItem: List<Item> = listOf(Item(...),
val direction:String = "START",
val parameterChange: Parameter = Parameter("New",14)
)
// ViewModel
class GViewModel: ViewModel() {
private val _uiState = MutableStateFlow(GUiState())
val uiState: StateFlow<GUiState> = _uiState.asStateFlow()
....
fun redirection(newDirection:String) {
_uiState.update { currentState -> currentState.copy(direction=newDirection)}
Log.d(TAG, "changed UIstate direction to ${uiState.value.direction}")
}
为了澄清结果,我可以在 logcat 中看到来自“重定向”函数的消息,从而确认 _uiState 的更新,但随后我再也没有看到来自 UiScreen 的关于路径新选择的另一条日志消息。
在“开始屏幕”中,您可以执行以下操作:
GViewModel().重定向(“CHOOSER”)
这将创建一个新的 GViewModel 实例(带有
GViewModel()
),您可以通过调用 redirection
来设置状态。
这不是您想要的:您想要更改已经存在的视图模型实例,因为只有该实例才会被观察到更改。要实现这一点,您需要向 StartScreen 传递应调用的函数:
@Composable
fun StartScreen(
redirection: (String) -> Unit,
modifier: Modifier = Modifier,
) {
// ...
Button(onClick = { redirection("CHOOSER") }) {
}
// ...
}
然后您可以像这样传递函数:
@Composable
fun BigBrother(gViewModel: GViewModel = viewModel()) {
val gUiState by gViewModel.uiState.collectAsState()
when (gUiState.direction) {
"START" -> StartScreen(redirection = gViewModel::redirection)
// ...
}
}
因此,每当您的 StartScreen 调用
redirection
时,实际执行的是
redirection
的 gViewModel 对象中的 BigBrother
。由于观察到该对象的 uiState
发生变化,现在它实际上会产生效果。