I'm working on a Jetpack Compose app where users can manage login profiles. The profiles are stored in a Room database, and the UI shows the profiles in a dropdown menu. The LaunchedEffect block in my LoginScreen is supposed to react to changes in the profile list (profiles), which is exposed as a StateFlow from the LoginViewModel.
When I edit or delete a profile, the LaunchedEffect is triggered, and the logs confirm that the updated profile list is fetched. However, when I add a new profile, the LaunchedEffect does not trigger.
view model code:
private val _profiles = MutableStateFlow<List<LoginProfile>>(emptyList())
val profiles: StateFlow<List<LoginProfile>> = _profiles
init {
fetchProfiles()
}
fun fetchProfiles() {
viewModelScope.launch {
val currentProfiles = database.loginProfileDao().getAllProfiles()
_profiles.value = currentProfiles
Log.d("Profile_Test", "Profiles fetched from DB: $_profiles")
}
}
fun saveProfileToDatabase(profile: LoginProfile) {
viewModelScope.launch {
database.loginProfileDao().insertProfile(profile)
fetchProfiles()
}
}
UI Code:
@Composable
fun ProfileDropdown(loginViewModel: LoginViewModel) {
var expanded by remember { mutableStateOf(false) }
var selectedItem by remember { mutableStateOf<LoginProfile?>(null) }
val profiles = loginViewModel.profiles.collectAsState().value
LaunchedEffect(profiles) {
Log.d("Profile_Test", "Launched Effect: $profiles")
if (profiles.isNotEmpty() && selectedItem != profiles.last()) {
selectedItem = profiles.last()
loginViewModel.selectedProfile.value = profiles.last()
} else if (profiles.isEmpty()) {
selectedItem = null
loginViewModel.selectedProfile.value = null
}
}
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = !expanded }
) {
OutlinedTextField(
value = selectedItem?.name ?: "Select Profile",
onValueChange = {},
readOnly = true,
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
}
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
profiles.forEach { profile ->
DropdownMenuItem(
text = { Text(profile.name) },
onClick = {
selectedItem = profile
loginViewModel.selectedProfile.value = profile
expanded = false
}
)
}
}
}
}
Why is LaunchedEffect not triggering when a new profile is added, even though _profiles
is updated? Is there something I'm missing in how StateFlow or LaunchedEffect works in Compose?
Any help or guidance would be greatly appreciated! Thanks!
You should let the Room database return the profiles in a Flow:
@Dao
interface LoginProfileDao {
@Query(...)
fun getAllProfiles(): Flow<List<LoginProfile>>
// ...
}
Then you can simplify the view model accordingly: You do not need _profiles
, fetchProfiles
and the init
block anymore. Just replace profiles
with this:
val profiles: StateFlow<List<LoginProfile>> = database.loginProfileDao().getAllProfiles()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
It takes the Room flow and converts it to a StateFlow. You don't need to call fetchProfiles()
anymore because the flow is automatically updated when anything changes in the database.
Your composable can stay the same, although it looks suspicious why you use a LaunchedEffect there, that doesn't seem to be necessary and should be removed.