Button OnClick not navigating to another screen

68 views Asked by At

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")
                }
            }
        }
    }
}
1

There are 1 answers

1
Jan Itor On BEST ANSWER

Try changing the way you get the CounterViewModel in the CounterScreen.

import androidx.lifecycle.viewmodel.compose.viewModel

fun CounterScreen(
    onNavigate: (UiEvent.Navigate) -> Unit,
    modifier: Modifier = Modifier,
    viewModel : CounterViewModel = viewModel(),
) {
//    val viewModel = CounterViewModel()

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 the CounterViewModel will be created on each CounterScreen recomposition. Because of that a CounterViewModel instance that being collected from in the LaunchedEffect and an instance used in the "Reset" button's onClick most likely will be different. With viewModels() function you will get the same instance of the CounterViewModel on every recomposition.