'Jetpack compose - how do I refresh a screen when app returns to foreground

I need to automatically refresh an Android Compose screen when the app returns to the foreground.

I have an that requires permissions and location services.

If the user has switched any of these off a list is drawn of the items that need to be changed. When the user goes to Settings and the app returns to the foreground I would like the list to refresh to reflect the changes.

I am using Compose and Compose navigation. I have looked and I can't figure out the equivalent of onResume lifecycle event that could be used to trigger the refresh.

Any ideas would be gratefully received as I am at a loss.



Solution 1:[1]

Edit: If you want a "pure" compose answer, check @JoJoIV 's answer

Answer:

Compose is not aware of state changes like onPause or onResume, you have to handle it using the parent activity's methods.

An example would be a LiveData instance in your activity that updates each time onResume is executed and observe it as a state in your main parent composable.

Let's take a look at the following example:

class MainActivity : AppCompatActivity() {
    // Use whatever type your prefer/require, this is just an example
    private val exampleLiveData = MutableLiveData("")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // Your main composable
            MyApplicationTheme {
                // Save the state into a variable otherwise it won't work
                val state = exampleLiveData.observeAsState()
                Log.d("EXAMPLE", "Recomposing screen - ${state.value}")

                Surface(color = MaterialTheme.colors.background) {
                    Greeting("Android")
                }
            }
        }
    }

    override fun onResume() {
        super.onResume()

        // Save whatever you want in your live data, this is just an example
        exampleLiveData.value = DateTimeFormatter.ISO_INSTANT.format(Instant.now())
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyApplicationTheme {
        Greeting("Android")
    }
}

As you can see in this example, I have a LiveData property in my activity that containts a String. Whenever onResume is executed the property is updated with the new timestamp and the observing composable is recomposed.

Solution 2:[2]

I came up with this:

@Composable
fun OnLifecycleEvent(onEvent: (owner: LifecycleOwner, event: Lifecycle.Event) -> Unit) {
    val eventHandler = rememberUpdatedState(onEvent)
    val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current)
    
    DisposableEffect(lifecycleOwner.value) {
        val lifecycle = lifecycleOwner.value.lifecycle
        val observer = LifecycleEventObserver { owner, event ->
            eventHandler.value(owner, event)
        }

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

It seems to work just fine. But there may be some issues in some cases so be careful.
It is also possible that there is some redundant code.

Usage:

OnLifecycleEvent { owner, event ->
    // do stuff on event
    when (event) {
        Lifecycle.Event.ON_RESUME -> { /* stuff */ }
        else                      -> { /* other stuff */ }
    }
}

Solution 3:[3]

I slightly improved @JojoIV answer and made it flat usage without callback like you observe LiveData in compose what @Abdelilah El Aissaoui answered

@Composable
fun Lifecycle.observeAsState(): State<Lifecycle.Event> {
    val state = remember { mutableStateOf(Lifecycle.Event.ON_ANY) }
    DisposableEffect(this) {
        val observer = LifecycleEventObserver { _, event ->
            state.value = event
        }
        [email protected](observer)
        onDispose {
            [email protected](observer)
        }
    }
    return state
}

and then usage

@Composable
fun SomeComposable() {
   val lifecycleState = LocalLifecycleOwner.current.lifecycle.observeAsState()
   val state = lifecycleState.value
   // or val lifecycleState by LocalLifecycleOwner.current.lifecycle.observeAsState()
  // will re-render someComposable each time lifecycleState will change
}

Solution 4:[4]

example from google site

@Composable
fun HomeScreen(
  lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
  onStart: () -> Unit, // Send the 'started' analytics event
  onStop: () -> Unit   // Send the 'stopped' analytics event
) {
    // Safely update the current lambdas when a new one is provided
    val currentOnStart by rememberUpdatedState(onStart)
    val currentOnStop by rememberUpdatedState(onStop)

    // If `lifecycleOwner` changes, dispose and reset the effect
    DisposableEffect(lifecycleOwner) {
        // Create an observer that triggers our remembered callbacks
        // for sending analytics events
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_START) {
                currentOnStart()
            } else if (event == Lifecycle.Event.ON_STOP) {
                currentOnStop()
            }
        }

        // Add the observer to the lifecycle
        lifecycleOwner.lifecycle.addObserver(observer)

        // When the effect leaves the Composition, remove the observer
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }

    /* Home screen content */
}

Full description of how it works in google site https://developer.android.com/jetpack/compose/side-effects#disposableeffect

Solution 5:[5]

i changed @ojoIV code to this (if your composable code is in Activity)

@Composable
fun ComponentActivity.LifecycleEventListener(event: (Lifecycle.Event) -> Unit) {
    val eventHandler by rememberUpdatedState(newValue = event)
    val lifecycle = [email protected]
    DisposableEffect(lifecycle) {
        val observer = LifecycleEventObserver { _, event ->
            eventHandler(event)
        }
        
        lifecycle.addObserver(observer)
        
        onDispose {
            lifecycle.removeObserver(observer)
        }
    }
}

usage

LifecycleEventListener(event = { lifecycleEvent ->
    when (lifecycleEvent ) {
        Lifecycle.Event.ON_CREATE -> {}
        Lifecycle.Event.ON_START -> {}
        Lifecycle.Event.ON_RESUME -> {}
        Lifecycle.Event.ON_PAUSE -> {}
        Lifecycle.Event.ON_STOP -> {}
        Lifecycle.Event.ON_DESTROY -> {}
        else -> return@LifecycleEventListener
    }
})

https://github.com/kafri8889/Notepad-Compose-Android/blob/master/app/src/main/java/com/anafthdev/notepadcompose/utils/ComposeUtils.kt

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Jakoss
Solution 4 Sergei S
Solution 5