Why do I need to wrap navActions.navigateToPurchase() with LaunchedEffect in fun NavGraph when use Jetpack Compose?

Issue

In my plan, I hope to display Home UI when I run an app first, a user can open Purchase UI by clicking a button on Home UI. And more, Purchase UI will be displayed automatically if the app is expired when I run the app first.

In Code A, navActions.navigateToPurchase() is invoked in both fun NavGraph(...) and fun ScreenHome(...).

The app will crash if I don’t wrap navActions.navigateToPurchase() with LaunchedEffect in fun NavGraph(), why? You can see Error Logs below.

Code A

@Composable
fun NavGraph(
    modifier: Modifier = Modifier,
    navController: NavHostController = rememberNavController(),
    navActions: NavigationActions = remember(navController) { NavigationActions(navController) },
    startDestination: String = RouteDestinations.HOME
) {
    val currentNavBackStackEntry by navController.currentBackStackEntryAsState()
    val currentRoute = currentNavBackStackEntry?.destination?.route ?: startDestination

    NavHost(
        navController = navController,
        startDestination = startDestination,
        modifier = modifier
    ) {

        composable(
            RouteDestinations.HOME
        ) {
            if (isAppExpired(LocalContext.current)) {
                LaunchedEffect(Unit) {
                    navActions.navigateToPurchase()
                }
            } else {
                ScreenHome(
                    navActions = navActions
                )
            }
        }

        composable(
            RouteDestinations.PURCHASE
        ) { entry ->
            ScreenPurchase(
                onBack = { navController.popBackStack() }
            )
        }
    }
}



@Composable
fun ScreenHome(
     navActions: NavigationActions
) {
    Button(
        onClick = { navActions.navigateToPurchase() }
    ) {
        Text("Nav to Purchase")
    }
}

class NavigationActions(private val navController: NavHostController) {
    fun navigateToHome(){
        navController.navigate(RouteDestinations.HOME)
    }   
 
    fun navigateToPurchase(){
        navController.navigate(RouteDestinations.PURCHASE)
    }
}

class ActivityMain : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            SoundMeterTheme {
                Surface(color = MaterialTheme.colors.background) {
                    NavGraph()
                }
            }
        }
    }
 }

Error Logs

2022-08-01 19:26:54.271 5611-5655/info.dodata.soundmeter E/cr_VariationsUtils: Failed reading seed file "/data/user/0/info.dodata.soundmeter/app_webview/variations_seed": /data/user/0/info.dodata.soundmeter/app_webview/variations_seed (No such file or directory)
2022-08-01 19:26:54.500 5611-5698/info.dodata.soundmeter E/chromium: [ERROR:gl_surface_egl.cc(335)] eglChooseConfig failed with error EGL_SUCCESS
2022-08-01 19:26:55.288 5611-5704/info.dodata.soundmeter E/eglCodecCommon: GoldfishAddressSpaceHostMemoryAllocator: ioctl_ping failed for device_type=5, ret=-1
2022-08-01 19:26:55.510 5611-5698/info.dodata.soundmeter E/chromium: [ERROR:gl_surface_egl.cc(335)] eglChooseConfig failed with error EGL_SUCCESS
2022-08-01 19:26:55.624 5611-5611/info.dodata.soundmeter E/AndroidRuntime: FATAL EXCEPTION: main
    Process: info.dodata.soundmeter, PID: 5611
    java.util.NoSuchElementException: List contains no element matching the predicate.
        at androidx.navigation.compose.NavHostKt$NavHost$4.invoke(NavHost.kt:180)
        at androidx.navigation.compose.NavHostKt$NavHost$4.invoke(NavHost.kt:141)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:116)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
        at androidx.compose.animation.CrossfadeKt$Crossfade$4$1.invoke(Crossfade.kt:115)
        at androidx.compose.animation.CrossfadeKt$Crossfade$4$1.invoke(Crossfade.kt:110)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
        at androidx.compose.animation.CrossfadeKt.Crossfade(Crossfade.kt:124)
        at androidx.compose.animation.CrossfadeKt.Crossfade(Crossfade.kt:55)
        at androidx.navigation.compose.NavHostKt.NavHost(NavHost.kt:141)
        at androidx.navigation.compose.NavHostKt.NavHost(NavHost.kt:67)
        at info.dodata.soundmeter.presentation.ui.NavGraphKt.NavGraph(NavGraph.kt:38)
        at info.dodata.soundmeter.presentation.ui.NavGraphKt$NavGraph$3.invoke(Unknown Source:21)
        at info.dodata.soundmeter.presentation.ui.NavGraphKt$NavGraph$3.invoke(Unknown Source:10)
        at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:157)
        at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2270)
        at androidx.compose.runtime.ComposerImpl.skipCurrentGroup(Composer.kt:2530)
        at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3038)
        at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3024)
        at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:252)
        at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
        at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3024)
        at androidx.compose.runtime.ComposerImpl.recompose$runtime_release(Composer.kt:2999)
        at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:709)
        at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:876)
        at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:107)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:485)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:454)
        at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame(AndroidUiFrameClock.android.kt:34)
        at androidx.compose.ui.platform.AndroidUiDispatcher.performFrameDispatch(AndroidUiDispatcher.android.kt:109)
        at androidx.compose.ui.platform.AndroidUiDispatcher.access$performFrameDispatch(AndroidUiDispatcher.android.kt:41)
        at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame(AndroidUiDispatcher.android.kt:69)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:947)
        at android.view.Choreographer.doCallbacks(Choreographer.java:761)
        at android.view.Choreographer.doFrame(Choreographer.java:693)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:935)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
        Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [[email protected], StandaloneCoroutine{Cancelling}@ee6482d, [email protected]]

Solution

for my simple knowledge, the navigate function need to be called in the function or click event, navActions.navigateToPurchase() was not called in any of the above, because function marked Composable is not like normal function or click event, that is why LauchEffect is used as side effect for such scenarios as explained by jetpack compose tutorials on google developer guide

Answered By – masokaya

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published