I'm trying to add an OnClick for a button, that navigates to another screen, however, it isn't working. My code is the same side by side with similar code, that is working (for another set of screens), but this one doesn't. I'm trying to get the "reset" button to trigger navigation to CounterEditScreen.
Navigation.kt:
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
fun Navigation(navController: NavHostController) {
NavHost(navController = navController, startDestination = Routes.TASKSLIST.route) {
composable(
Routes.TASKSLIST.route
) {
TasksListScreen(onNavigate = {
navController.navigate(it.route)
})
}
composable(
route = Routes.ADDEDITTASK.route + "?taskId={taskId}",
arguments = listOf(navArgument(name = "taskId") {
type = NavType.IntType
defaultValue = -1
})
) {
AddEditScreen(
onPopBackStack = {
navController.popBackStack()
})
}
composable(
Routes.COUNTER.route
) {
CounterScreen(onNavigate = {
navController.navigate(it.route)
})
}
composable(
route = Routes.COUNTEREDIT.route
) {
CounterEditScreen(
onPopBackStack = {
navController.popBackStack()
}
)
}
}
}
CounterViewModel:
class CounterViewModel : ViewModel() {
private val _uiEvent = Channel<UiEvent>()
val uiEvent = _uiEvent.receiveAsFlow()
fun onEvent(event: CounterEvent) {
when (event) {
CounterEvent.OnButtonSwitch -> TODO()
is CounterEvent.OnCounterNameChange -> TODO()
is CounterEvent.OnCounterReset -> {
viewModelScope.launch(Dispatchers.IO) {
sendUiEvent(UiEvent.Navigate(Routes.COUNTEREDIT.route))
}
}
is CounterEvent.OnDrawerNavigationClick -> {
sendUiEvent(UiEvent.Navigate(event.navigationItem.route))
}
}
}
private fun sendUiEvent(event: UiEvent) {
viewModelScope.launch(Dispatchers.IO) {
_uiEvent.send(event)
}
}
}
CounterEvent:
sealed class CounterEvent {
object OnCounterNameChange : CounterEvent()
data class OnDrawerNavigationClick(val navigationItem: NavigationItem) : CounterEvent()
object OnCounterReset : CounterEvent()
object OnButtonSwitch : CounterEvent()
}
Counter.kt:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CounterScreen(
modifier: Modifier = Modifier,
onNavigate: (UiEvent.Navigate) -> Unit,
) {
val viewModel = CounterViewModel()
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
var selectedItemIndex by rememberSaveable {
mutableIntStateOf(0)
}
val days by rememberSaveable {
mutableIntStateOf(0)
}
val counterName by rememberSaveable {
mutableStateOf("Cold showers")
}
LaunchedEffect(key1 = true) {
viewModel.uiEvent.collectLatest { event ->
when (event) {
is UiEvent.Navigate -> {
onNavigate(event)
}
else -> Unit
}
}
}
ModalNavigationDrawer(drawerContent = {
ModalDrawerSheet {
items.forEachIndexed { index, item ->
Spacer(modifier = Modifier.height(12.dp))
NavigationDrawerItem(
label = { Text(item.title) },
selected = index == selectedItemIndex,
onClick = {
viewModel.onEvent(CounterEvent.OnDrawerNavigationClick(item))
selectedItemIndex = index
coroutineScope.launch {
drawerState.close()
}
},
icon = {
Icon(
imageVector = if (index == selectedItemIndex) {
item.selectedIcon
} else item.unselectedIcon, contentDescription = item.title
)
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
}
}
}, drawerState = drawerState) {
Scaffold(
snackbarHost = {
SnackbarHost(snackbarHostState) { data ->
Snackbar(
shape = RoundedShapes.medium,
actionColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.background,
snackbarData = data
)
}
},
topBar = {
TopAppBar(
title = { Text("Day counter") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.background
),
navigationIcon = {
IconButton(onClick = {
coroutineScope.launch { drawerState.open() }
}) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = "Menu"
)
}
}
)
},
) { padding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.fillMaxSize()
.padding(padding)
.padding(horizontal = 24.dp)
) {
Spacer(modifier = Modifier.height(32.dp))
Text(
text = "$counterName\ndays passed: $days",
style = MaterialTheme.typography.headlineMedium,
modifier = modifier.clickable {
viewModel.onEvent(CounterEvent.OnCounterNameChange)
},
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(32.dp))
Button(
onClick = { viewModel.onEvent(CounterEvent.OnButtonSwitch) },
modifier = modifier.fillMaxWidth()
) {
Text(text = "Start")
}
Spacer(modifier = Modifier.height(4.dp))
Button(
onClick = { viewModel.onEvent(CounterEvent.OnCounterReset) },
modifier = modifier.fillMaxWidth()
) {
Text(text = "Reset")
}
}
}
}
}
Try changing the way you get the
CounterViewModelin theCounterScreen.You might have to add
androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1dependency.My theory: with this
val viewModel = CounterViewModel()code a new instance of theCounterViewModelwill be created on eachCounterScreenrecomposition. Because of that aCounterViewModelinstance that being collected from in theLaunchedEffectand an instance used in the "Reset" button'sonClickmost likely will be different. WithviewModels()function you will get the same instance of theCounterViewModelon every recomposition.