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
CounterViewModel
in theCounterScreen
.You might have to add
androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1
dependency.My theory: with this
val viewModel = CounterViewModel()
code a new instance of theCounterViewModel
will be created on eachCounterScreen
recomposition. Because of that aCounterViewModel
instance that being collected from in theLaunchedEffect
and an instance used in the "Reset" button'sonClick
most likely will be different. WithviewModels()
function you will get the same instance of theCounterViewModel
on every recomposition.