android-jetpack-composeandroid-architecture-navigationjetpack-compose-navigation

Jetpack Compose + Navigation: rememberSaveable loses state on rotate


I've encountered a strange behavior with Jetpack Compose in combination with Navigation: If you use rememberSaveable inside some navigation composable, then the state is not saved as promised (e.g. it is lost after rotation). Here is a simple example:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.MaterialTheme
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                MyScreen()
            }
        }
    }
}

@Composable
fun MyScreen() {
    val navController = rememberNavController()
    NavHost(navController = navController, startDestination = "xyz") {
        composable("xyz") {
            var value by rememberSaveable { mutableStateOf("") }
            TextField(value = value, onValueChange = { value = it })
        }
    }
}

The above code produces a single text field in which one can type. Once one rotates the screen, the typed text is lost, even though the value should be saved by rememberSaveable.

Investigating a little bit, I noticed the following:

  • The problem is really the NavHost. If one moves the line defining the variable "value" to the top of "MyScreen()" (outside of the NavHost) then everything works as intended.

  • The real issue seems to be that the composable variable "currentCompositeKeyHash" is not retained after configuration changes inside the NavHost. This variable is used as a key for the savedInstanceBundle to retrieve the saved value by rememberSaveable, thus the state gets lost. In particular, if one explicitly specifies a key in rememberSaveable, then everything works as expected.

Is this a bug or am I misunderstanding something?


Solution

  • Update:

    Version 2.4.0-alpha07 has been released and fixes the issue:

    implementation "androidx.navigation:navigation-compose:2.4.0-alpha07"
    

    Original answer:

    This is a known issue with version 2.4.0-alpha05 and 2.4.0-alpha06 of androidx.navigation:navigation-compose. The current solution is to downgrade to 2.4.0-alpha04:

    implementation "androidx.navigation:navigation-compose:2.4.0-alpha04"
    

    According to the issue tracker, the issue has been fixed in version 2.4.0-alpha07, which hopefully will be released soon.