我的视图在更改状态后多次重新组合

Ber*_*çci 4 android kotlin android-jetpack-compose

我正在从事撰写项目。我有简单的登录页面。单击登录按钮后,登录状态在视图模型中设置。问题是当我在服务调用后设置 loginState 时,我的可组合项会多次重新组合。因此,navcontroller 会进行多次导航。我不明白这个问题。谢谢你的帮助。

我的可组合项:

@Composable
fun LoginScreen(
    navController: NavController,
    viewModel: LoginViewModel = hiltViewModel()
) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.SpaceEvenly
    ) {
        val email by viewModel.email
        val password by viewModel.password
        val enabled by viewModel.enabled

        if (viewModel.loginState.value) {
            navController.navigate(Screen.HomeScreen.route) {
              popUpTo(Screen.LoginScreen.route) {
                 inclusive = true
              }
            }
        }

        LoginHeader()
        LoginForm(
            email = email,
            password = password,
            onEmailChange = { viewModel.onEmailChange(it) },
            onPasswordChange = { viewModel.onPasswordChange(it) }
        )
        LoginFooter(
            enabled,
            onLoginClick = {
                viewModel.login()
            },
            onRegisterClick = {
                navController.navigate(Screen.RegisterScreen.route)
            }
        )
    }
Run Code Online (Sandbox Code Playgroud)

视图模型类:

@HiltViewModel
class LoginViewModel @Inject constructor(
    private val loginRepository: LoginRepository,
) : BaseViewModel() {

    val email = mutableStateOf(EMPTY)
    val password = mutableStateOf(EMPTY)
    val enabled = mutableStateOf(false)
    val loginState = mutableStateOf(false)

    fun onEmailChange(email: String) {
        this.email.value = email
        checkIfInputsValid()
    }

    fun onPasswordChange(password: String) {
        this.password.value = password
        checkIfInputsValid()
    }

    private fun checkIfInputsValid() {
        enabled.value =
            Validator.isEmailValid(email.value) && Validator.isPasswordValid(password.value)
    }

    fun login() = viewModelScope.launch {
        val response = loginRepository.login(LoginRequest(email.value, password.value))
        loginRepository.saveSession(response)
        loginState.value = response.success ?: false
    }
}
Run Code Online (Sandbox Code Playgroud)

Phi*_*hov 6

您不应直接从可组合构建器中产生副作用或更改状态,因为这将在每次重组时执行。

相反,您可以使用副作用。在你的情况下,LaunchedEffect可以使用。

if (viewModel.loginState.value) {
    LaunchedEffect(Unit) {
        navController.navigate(Screen.HomeScreen.route) {
            popUpTo(Screen.LoginScreen.route) {
                inclusive = true
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但我认为更好的解决方案不是监听 的变化loginState,而是创建login一个挂起函数,等待它完成然后执行导航。您可以获得一个协程作用域,它将绑定到您的可组合项rememberCoroutineScope。它可以看起来像这样:

suspend fun login() : Boolean {
    val response = loginRepository.login(LoginRequest(email.value, password.value))
    loginRepository.saveSession(response)
    return response.success ?: false
}
Run Code Online (Sandbox Code Playgroud)

另请查看 Google 工程师关于为什么不应在此答案NavController中作为参数传递的想法(根据 Navigation Compose 测试指南...


因此,更新后您的视图将如下所示:

@Composable
fun LoginScreen(
    viewModel: LoginViewModel = hiltViewModel(),
    onLoggedIn: () -> Unit,
    onRegister: () -> Unit,
) {
    // ...
    val scope = rememberCoroutineScope()
    LoginFooter(
        enabled,
        onLoginClick = {
            scope.launch {
                if (viewModel.login()) {
                    onLoggedIn()
                }
            }
        },
        onRegisterClick = onRegister
    )
    // ...
}
Run Code Online (Sandbox Code Playgroud)

以及您的导航路线:

composable(route = "login") {
    LoginScreen(
        onLoggedIn = {
            navController.navigate(Screen.HomeScreen.route) {
                popUpTo(Screen.LoginScreen.route) {
                    inclusive = true
                }
            }
        },
        onRegister = {
            navController.navigate(Screen.RegisterScreen.route)
        }
    )
}
Run Code Online (Sandbox Code Playgroud)