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