2

Composable函数Application创建一个NavHostController定义 2 个目标的函数。一个StartScreen和一个ContentScreen。只有StartScreen一个按钮,它会触发模拟的后端请求并StateFlow根据请求的状态更改状态(使用 kotlins )。当结果返回时,将调用NavControllersnavigate方法以在ContentScreen.

问题:状态InitLoading工作正常,但是一旦应该显示内容,ContentScreen就会在循环中重新绘制并且不会停止。

我在这里做错了什么?


/** Dependencies
    implementation 'androidx.core:core-ktx:1.6.0'
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation "androidx.compose.ui:ui:1.0.2"
    implementation "androidx.compose.ui:ui:1.0.2"
    implementation "androidx.compose.material:material:1.0.2"
    implementation "androidx.compose.ui:ui-tooling-preview:1.0.2"
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
    implementation "androidx.navigation:navigation-compose:2.4.0-alpha08"
    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07'
    implementation 'androidx.activity:activity-compose:1.3.1'
 *
 * **/


sealed class MainState {
    object Init : MainState()
    object Loading : MainState()
    data class Content(val data: String) : MainState()
}

class MainViewModel : ViewModel() {
    private val _state = MutableStateFlow<MainState>(MainState.Init)
    val state: StateFlow<MainState> = _state

    fun dosomething() {
        viewModelScope.launch {
            _state.value = MainState.Loading
            // emulating some BE call
            delay(4000)
            _state.value = MainState.Content("some backend result")
        }
    }
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Application()
        }
    }
}


@Composable
fun Application() {
    val navController = rememberNavController()
    NavHost(navController = navController, startDestination = "start") {
        composable("start") {
            val viewmodel: MainViewModel = viewModel()
            val state by viewmodel.state.collectAsState()
            when (val state = state) {
                is MainState.Content -> navController.navigate("content/${state.data}")
                is MainState.Loading -> LoadingScreen()
                MainState.Init -> StartScreen()
            }

        }
        composable(
            "content/{content}",
            arguments = listOf(
                navArgument("content") {
                    type = NavType.StringType
                }
            )
        ) {
            ContentScreen(content = it.arguments!!.getString("content")!!)
        }
    }
}


@Composable
fun LoadingScreen() {
    Box(
        contentAlignment = Alignment.Center,
        modifier = Modifier.fillMaxSize()
    ) {
        CircularProgressIndicator()
    }
}

@Composable
fun StartScreen(viewmodel: MainViewModel = viewModel()) {
    Box(
        contentAlignment = Alignment.Center,
        modifier = Modifier.fillMaxSize()
    ) {
        Button(onClick = { viewmodel.dosomething() }) {
            Text(text = "Click Me!")
        }
    }
}

@Composable
fun ContentScreen(content: String) {
    Box(
        contentAlignment = Alignment.Center,
        modifier = Modifier.fillMaxSize()
    ) {
        Card(modifier = Modifier.padding(8.dp)) {
            Text(text = content)
        }
    }
}

4

1 回答 1

6

在 compose 中,您正在使用视图构建器创建 UI。这个函数可以多次调用,当你开始使用动画时,它甚至可以在每一帧上重新组合。

这就是为什么您不应该直接从可组合函数执行任何副作用。你需要使用副作用

在这种情况下LaunchedEffect(Unit)应该使用:里面的代码只会被启动一次。

when (val state = state) {
    is MainState.Content -> LaunchedEffect(Unit) {
        navController.navigate("content/${state.data}")
    }
    is MainState.Loading -> LoadingScreen()
    MainState.Init -> StartScreen()
}
于 2021-09-09T08:07:03.633 回答