KotlinLeaner
03/08/2024, 11:25 PMnavigateToDeviceSelection, leading to redirection to ScreenName.ScreenOne and the storage of this value within destinationState. Subsequently, when navigating via navController, the destination state remains unchanged, resulting in redirection to another screen. Additionally, upon invoking navigateToDeviceSelection within ScreenName.ScreenOne.ChildTwo.route, the destination state fails to update, rendering the LaunchedEffect ineffective, and causing the user to remain stuck in the ScreenName.ScreenOne.ChildTwo.route screen. To address this challenge, I seek a solution that avoids accessing destinationState within nested composable functions, as this example is basic, and I prefer not to pass this variable to nested children.KotlinLeaner
03/08/2024, 11:26 PMclass MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NavigationScreen()
}
}
}
MainViewModel
class MainViewModel(private val navigationHandler: NavigationHandler) : ViewModel() {
val currentDestination: StateFlow<ScreenName?> =
navigationHandler.destination.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(),
initialValue = null
)
fun navigateToDeviceSelection(isValid: Boolean) {
val destination = if (isValid) {
ScreenName.ScreenOne
} else {
ScreenName.ScreenTwo
}
println(">> destination ${destination.route}")
navigationHandler.navigate(destination)
}
}
I created a custom navigation class which handle the logic and navigate the screen accordingly using StateFlow.
NavigationDestination
interface NavigationDestination {
val route: String
}
NavigationHandler
interface NavigationHandler {
val destination: StateFlow<ScreenName?>
fun navigate(navigationDestination: ScreenName)
}
Navigator
class Navigator : NavigationHandler {
private val _destination: MutableStateFlow<ScreenName?> = MutableStateFlow(null)
override val destination: StateFlow<ScreenName?> = _destination.asStateFlow()
override fun navigate(navigationDestination: ScreenName) {
_destination.value = navigationDestination
}
}
ScreenName
sealed class ScreenName(override val route: String) : NavigationDestination {
object ScreenOne : ScreenName("ScreenOne") {
object ChildOne : ScreenName("ChildOne")
object ChildTwo : ScreenName("ChildTwo")
}
object ScreenTwo : ScreenName("ScreenTwo")
}
Now when I consume this MainViewModel inside composable like below code
@Composable
fun NavigationScreen(
viewModel: MainViewModel = koinViewModel(),
navController: NavHostController = rememberNavController()
) {
val destinationState by viewModel.currentDestination.collectAsStateWithLifecycle()
LaunchedEffect(destinationState) {
destinationState?.let {
navController.navigate(it.route) {
popUpTo(navController.graph.startDestinationId) {
inclusive = true
}
launchSingleTop = true
}
}
}
LaunchedEffect(viewModel) {
viewModel.navigateToDeviceSelection(true)
}
NavHost(
navController = navController,
startDestination = ScreenName.ScreenOne.route,
route = "parentRoute"
) {
nestedGraphSample(navController, viewModel)
composable(ScreenName.ScreenTwo.route) {
Text(text = "Screen Two Route")
}
}
}
private fun NavGraphBuilder.nestedGraphSample(
navController: NavHostController,
viewModel: MainViewModel
) {
navigation(
startDestination = ScreenName.ScreenOne.ChildOne.route,
route = ScreenName.ScreenOne.route
) {
composable(ScreenName.ScreenOne.ChildOne.route) {
LaunchedEffect(Unit) {
delay(5.seconds)
navController.navigate(ScreenName.ScreenOne.ChildTwo.route) {
popUpTo(navController.graph.startDestinationId) {
inclusive = true
}
launchSingleTop = true
}
}
Text(text = "Screen Child One Route")
}
composable(ScreenName.ScreenOne.ChildTwo.route) {
LaunchedEffect(Unit) {
delay(5.seconds)
viewModel.navigateToDeviceSelection(true)
}
Text(text = "Screen Child Two Route")
}
}
}Stylianos Gakis
03/09/2024, 9:52 AMKotlinLeaner
03/09/2024, 12:02 PMStylianos Gakis
03/09/2024, 3:02 PMKotlinLeaner
03/11/2024, 6:37 PM