I have a run of the mill jetpack app, with a Scaffold setup. The top bar composable is supposed to display the username once they log in, but recomposition is not triggered. This is the code (I replaced the regular top bar with a simple Text, for simplicity)
class AppActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
EdocClientTheme {
val navCtrl = rememberNavController()
Scaffold(modifier = Modifier.fillMaxSize(), topBar = { TopBar(navCtrl) }) { innerPadding ->
NavigationContainer(navCtrl = navCtrl, modifier = Modifier.padding(innerPadding))
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopBar(navCtrl: NavHostController) {
val vm = viewModel<AppViewModel>()
val loggedIn = vm.loggedIn.collectAsStateWithLifecycle()
Log.i("DEBUG MSG", "Recomposing TopBar")
Text("---${loggedIn.value}", modifier = Modifier.padding(24.dp))
}
This is the ViewModel in which I added a subscription to the loggedIn flow to check whether the value is actually updated
class AppViewModel : ViewModel(){
private val _loggedIn = MutableStateFlow<User?>(null)
val loggedIn : StateFlow<User?> = _loggedIn
private val _loginError = MutableStateFlow<String?>(null)
val loginError : StateFlow<String?> = _loginError
init {
viewModelScope.launch {
loggedIn.collectLatest{ u -> Log.i("DEBUG MSG", "Logged in is now ${u}")}
}
}
fun logIn (username : String, password : String){
viewModelScope.launch {
try {
_loggedIn.value = ApiClient.login(username, password)
} catch (e : Exception){
_loginError.value = e.message
Log.e("LOGIN_ERROR", e.message, e)
}
}
}
}
When the login function is invoked, I can see the log message printed by the ViewModel coroutine, and also, another composable that depends on the loggedIn state in the same way as TopBar is recomposed, but TopBar isn’t. Is there something particular about composables being passed as parameters that makes them work differently?
4
I had different ViewModel instances, as Raghunandan suggested. In one call to viewModel() the implicit ViewModelStoreOwner was the activity, while, in the other, it was the current navigation back stack entry. After I started providing the parameter viewModelStoreOwner explicitly as the activity to each call, the problem went away.
I’m still having a hard time understanding how the implicit current value of the ViewModelStoreOwner is set by the framework, if anybody can point me to any resources that clarify this, I’d be grateful