Search code examples
androidandroid-lifecycleandroid-jetpack-compose

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

  • Using oficial API (requires Lifecycle Runtime Compose 2.7.0):

    implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")
    

    The official way to detect Lifecycle.State changes would be using the following code snippet:

    val lifecycleOwner = LocalLifecycleOwner.current
    val lifecycleState by lifecycleOwner.lifecycle.currentStateFlow.collectAsState()
    
    LaunchedEffect(lifecycleState) {
        // Do something with your state
        // You may want to use DisposableEffect or other alternatives
        // instead of LaunchedEffect
        when (lifecycleState) {
            Lifecycle.State.DESTROYED -> {}
            Lifecycle.State.INITIALIZED -> {}
            Lifecycle.State.CREATED -> {}
            Lifecycle.State.STARTED -> {}
            Lifecycle.State.RESUMED -> {}
        }
    }
    

    collectStateAsState is a convenience extension function that collects State changes by using Lifecycle's new property currentStateFlow. So the code above would be the same as doing:

    val lifecycleOwner = LocalLifecycleOwner.current
    val state by lifecycleOwner.lifecycle.currentStateFlow.collectAsState()
    
    LaunchedEffect(state) {
        // Do something with your state
        // You may want to use DisposableEffect or other alternatives 
        // instead of LaunchedEffect
    }
    

    Remember to import androidx.compose.runtime.getValue to access directly the Lifecycle.State.

    As mentioned by @HåkonSchia, you can also use variations of DiposableEffect that also take in consideration the Lifecycle:

    LifecycleStartEffect(Unit) {
        // Do something on start or launch effect
    
        onStopOrDispose {
            // Do something on stop or dispose effect
        }
    }
    
    LifecycleResumeEffect(Unit) {
        // Do something on resume or launch effect
    
        onPauseOrDispose {
            // Do something on pause or dispose effect
        }
    }
    

    Old 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.