LifecycleStartEffect.onStopOrDispose is called twice on navigating to another screen
Asked Answered
M

0

5

So I was trying to use androidx.lifecycle:lifecycle-runtime-compose to implement onStart/onStop events for my Composable screens:

In MainScreen I have:

LifecycleStartEffect(Unit) {
    Timber.d("MainScreen LifeCycle ON_START ${System.currentTimeMillis()}")  

    onStopOrDispose {
        // Provide the onStopOrDisposeEffect to the LifecycleStartEffect 
        // to run when the observer receives an (ON_STOP) Lifecycle.Event.ON_STOP 
        // event or must undergo cleanup.

        // WILL BE CALLED TWICE on navigating to another screen

        Timber.d("MainScreen LifeCycle ON_STOP ${System.currentTimeMillis()}")  
    }
}

When I navigate from this screen (MainScreen) to another one (e.g.: SettingsScreen)

@Composable
fun MainScreen(
    ...
    navigateToSettings: () -> Unit,
) {

// navigation

NavHost(
    navController = navController,
    route = mainGraphRoutePattern,
    startDestination = startDestination,
    modifier = modifier
) {
    mainScreen(
        ...
        navigateToSettings = navController::navigateToSettings,
    )
    settingsScreen(
        ...
        navigateUp = navController::navigateUpWithLifecycle
    )

I notice that ON_STOP is called two times:

19:54:43.918  D  MainScreen LifeCycle ON_START 1706291683918
19:54:45.711  D  MainScreen LifeCycle ON_STOP 1706291685710
19:54:46.566  D  MainScreen LifeCycle ON_STOP 1706291686565

It's only called once if I'm not navigating to another screen but just leaving the screen (on back/home button pressed)

Then I decided to remove LifecycleStartEffect and use LifecycleEventEffect instead:

LifecycleEventEffect(event = Lifecycle.Event.ON_START) {
    if (DEBUG) Timber.d("MainScreen LifeCycle ON_START ${System.currentTimeMillis()}")
}

LifecycleEventEffect(event = Lifecycle.Event.ON_STOP) {
    if (DEBUG) Timber.d("MainScreen LifeCycle ON_STOP ${System.currentTimeMillis()}")
}

And now it works fine (ON_STOP is called only once):

20:02:19.916  D  MainScreen LifeCycle ON_START 1706292139916
20:02:33.860  D  MainScreen LifeCycle ON_STOP 1706292153859

It has general implementation to observe events:

@Composable
fun LifecycleEventEffect(
    event: Lifecycle.Event,
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    onEvent: () -> Unit
) {
    if (event == Lifecycle.Event.ON_DESTROY) {
        throw IllegalArgumentException("LifecycleEventEffect cannot be used to " +
            "listen for Lifecycle.Event.ON_DESTROY, since Compose disposes of the " +
            "composition before ON_DESTROY observers are invoked.")
    }

    // Safely update the current `onEvent` lambda when a new one is provided
    val currentOnEvent by rememberUpdatedState(onEvent)
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, e ->
            if (e == event) {
                currentOnEvent()
            }
        }

        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}

But what's wrong with LifecycleStartEffect then? It's available in the same library as well.

Is it a bug in this implementation?

p.s. info about LifecycleStartEffect: https://developer.android.com/jetpack/androidx/releases/lifecycle

Matherne answered 26/1, 2024 at 18:3 Comment(1)
You'll want to file an issue if you want someone on the Lifecycle team to look at your issue.Arterial

© 2022 - 2025 — McMap. All rights reserved.