I have a main composable that wraps a NavHost
to display different screens. Some of these screens need to navigate to other screens based on state changes in a ViewModel
that happen as a result of method calls. Here's a trimmed down example of what I have at the moment:
class ExampleViewModel(application: Application) : AndroidViewModel(application) {
// Used by a
var error: String? by mutableStateOf(null)
private set
var user: User? by mutableStateOf(null)
private set
fun onLogin(email: String, password: String) {
viewModelScope.launch {
doLogin(email, password)
.onSuccess { user = it }
.onFailure { error = it.localizedMessage }
}
}
}
@Composable
fun LoginScreen(
navController: NavController,
exampleViewModel: ExampleViewModel,
) {
DisposableEffect(exampleViewModel.user) {
if (exampleViewModel.user != null) {
navController.navigate("somewhere")
}
onDispose {}
}
var email by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
// Email TextField.
// Password TextField.
Button(onClick = { exampleViewModel.onLogin(email, password) }) {
Text("Login")
}
}
The error is handled like this in a composable up above:
LaunchedEffect(exampleViewModel.error) {
exampleViewModel.error?.let { scaffoldState.snackbarHostState.showSnackbar(it) }
}
Using a DisposableEffect
in this way seems kind of dirty, and quite error prone. On top of that, this error handling method makes it difficult to, for example, disable the login form while the login is pending. Would it be better to make onLogin()
suspend
and handle its success and failures, and corresponding local state, in a callback inside of LoginScreen
? The downside to that is that the login screen will no longer automatically redirect if it's navigated to while already logged in. snapshotFlow
in a LaunchedEffect(true)
is another thing I've considered, but that doesn't really seem to have any particular benefits over DisposableEffect
.
What's the correct way to do this? Am I on completely the wrong track here?