Vladimir
08/05/2024, 5:45 PMLaunchedEffect
doesn't re-render when the mutable state changes inside the ViewModelVladimir
08/05/2024, 5:47 PMHome
screen when the user enters their credentials and presses the login button inside the Login
composable.
The LaunchedEffect
with this redirect logic is located in the main MyApp
composable wrapper.
MyApp
composable wrapper that is run from within `MainActivity`:
@Composable
fun MyApp(sessionViewModel: SessionViewModel = hiltViewModel()) {
val navController = rememberNavController()
MyAppNavHost(navController)
val sessionStatus by sessionViewModel.sessionStatus
LaunchedEffect(sessionStatus) {
println("RERENDERED") // gets printed only once
if (!sessionStatus) {
sessionViewModel.checkSession()
}
if (sessionStatus) {
navController.navigate(Home) {
popUpTo(Login) { inclusive = true }
}
} else {
navController.navigate(route = Login) {
popUpTo(Home) { inclusive = true }
}
}
}
}
Session ViewModel
that contains the state variables and the function to update them:
@HiltViewModel
class SessionViewModel @Inject constructor(private val sessionManager: SessionManager) : ViewModel() {
private val _sessionStatus : MutableState<Boolean> = mutableStateOf(false)
var sessionStatus: MutableState<Boolean> = _sessionStatus
fun createSession(credentials: Credentials) {
viewModelScope.launch {
_sessionStatus.value = sessionCreated.isSuccess
_isAuthenticating.value = false
}
}
}
The LoginScreen
composable contains the input fields and a button that, when pressed, triggers createSession
in the viewmodel. The login itself is successful, however the LaunchedEffect
inside MyApp
does not trigger a rerender despite the _sessionStatus
value gets updated inside the session viewmodel.
What am I doing wrong? Could it be that it is because the button press originates inside a LoginScreen
composable and not inside the MyApp
composable and the latter cannot detect the sessionStatus change that this press triggers in the viewmodel?Vladimir
08/05/2024, 5:54 PMSessionViewModel
instance that is tied to the LoginScreen
and therefore not visible MyApp
?
fun NavGraphBuilder.loginScreenGraph() {
composable<Login> {
val sessionViewModel: SessionViewModel = hiltViewModel()
LoginScreen(onLogin = { credentials -> sessionViewModel.createSession(credentials) }, isAuthenticating = sessionViewModel.isAuthenticating.value)
}
}
@Serializable
object Login
Ian Lake
08/05/2024, 5:56 PMIan Lake
08/05/2024, 5:57 PMLocalViewModelStoreOwner.current
at the MyApp level, you can pass that down to your screen and pass that to hiltViewModel
Zach Klippenstein (he/him) [MOD]
08/05/2024, 5:58 PMIan Lake
08/05/2024, 5:59 PMVladimir
08/05/2024, 5:59 PMVladimir
08/05/2024, 6:00 PMvar sessionStatus -> val sessionStatus
Ian Lake
08/05/2024, 6:01 PMVladimir
08/05/2024, 6:02 PMVladimir
08/05/2024, 6:02 PMVladimir
08/05/2024, 6:02 PMIan Lake
08/05/2024, 6:04 PMval owner = LocalViewModelStoreOwner.current
, fun NavGraphBuilder.loginScreenGraph(owner: ViewModelStoreOwner)
, and hiltViewModel(owner)
Ian Lake
08/05/2024, 6:04 PMIan Lake
08/05/2024, 6:06 PMVladimir
08/05/2024, 6:07 PMIan Lake
08/05/2024, 6:08 PMVladimir
08/05/2024, 6:08 PM@Composable
fun MyAppNavHost(navController: NavHostController) {
NavHost(navController = navController, startDestination = Login) {
homeScreenGraph()
loginScreenGraph()
}
}
Vladimir
08/05/2024, 6:09 PMMyAppNavHost
function and pass it down?Ian Lake
08/05/2024, 6:10 PMLocalViewModelStoreOwner
, so either spot is fine for creating the variableVladimir
08/05/2024, 6:12 PMIan Lake
08/05/2024, 6:14 PM!!
or checkNotNull()
around it let the compiler know you know it will always be availableVladimir
08/05/2024, 6:18 PMsince they are both outside the NavHost, will have the sameLocalViewModelStoreOwner
Ian Lake
08/05/2024, 6:19 PMVladimir
08/05/2024, 6:19 PMval owner = LocalViewModelStoreOwner.current!!
the conventional way to go about it? Or is there a way to actually check for null and somehow troubleshoot it?Vladimir
08/05/2024, 6:20 PMIan Lake
08/05/2024, 6:20 PMcheckNotNull()
optionVladimir
08/05/2024, 6:21 PM