Jetpack Compose: Managing Shared UI State

Viewed 747
typealias Actions = @Composable () -> Unit

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            SampleAppTheme {
                var currentScreen by remember { mutableStateOf(Screen.A) }
                var title by remember { mutableStateOf("Screen A") }
                var action by remember { mutableStateOf<Actions>({}) }
                
                Scaffold(
                     topBar = {
                        TopAppBar(
                            title = {
                                Text(title)
                            },
                            actions = {
                                action()
                            }
                        )
                    }
                 ) {
                    Crossfade(current = currentScreen) { screen ->
                         when (screen) {
                             Screen.A -> {
                                MyScreen(
                                    name = "A",
                                    buttonName = "to Screen B",
                                    onNext = {
                                        currentScreen = Screen.B
                                        title = "Screen B"
                                        action = {
                                             IconButton(onClick = {}) {
                                                Icon(asset = Icons.Sharp.Home)
                                            }
                                        }
                                    }
                                )
                            }
                            Screen.B -> {
                                MyScreen(
                                    name = "B",
                                    buttonName = "to Screen C",
                                    onNext = {
                                        currentScreen = Screen.C
                                        title = "Screen B"
                                        action = {
                                            IconButton(onClick = {}) {
                                                Icon(asset = Icons.Sharp.Close)
                                            }
                                        }
                                    }
                                )
                            }
                            Screen.C -> {
                                MyScreen(
                                    name = "C",
                                    buttonName = "to Screen A",
                                    onNext = {
                                        currentScreen = Screen.A
                                        title = "Screen A"
                                        action = {}
                                    }
                                )
                            }
                        }
                    }
                }
            }
        }
    }
}

enum class Screen {
    A, B, C
}

@Composable
fun MyScreen(
    name: String,
    buttonName: String,
    onNext: () -> Unit,
) {
    Column(
        Modifier.fillMaxSize().padding(24.dp),
    ) {
        Text(
            modifier = Modifier.weight(1f),
            text = "I am in screen $name!",
            textAlign = TextAlign.Center,
            style = MaterialTheme.typography.h3
        )
        Button(
            modifier = Modifier.fillMaxWidth(),
            onClick = onNext
        ) {
            Text(text = buttonName)
        }
    }
}

So with this code I have the following screen:

ScreenAScreenBScreenC

Notice that I am able to change the Actions in the TopAppBar, depending on the Current Screen by doing:

MyScreen(
    name = "B",
    buttonName = "to Screen C",
    onNext = {
        currentScreen = Screen.C
        title = "Screen B"
        // This right here!!!! -------------------
        action = { 
            IconButton(onClick = {}) {
                Icon(asset = Icons.Sharp.Close)
            }
        }
        // This right here!!!! -------------------
    }
)
  1. My first question is whether this is a correct way to this?

  2. For the second question, I kinda feel the values I am setting in the previous screen should instead be the responsibility of the current screen instead. so I tried doing this instead?

Screen.B -> {
    // This right here!!!! -------------------
    onActive {
         title = "Screen B"
         action = { 
             IconButton(onClick = {}) {
                 Icon(asset = Icons.Sharp.Home)
             }
         }
    }
    // This right here!!!! -------------------
    MyScreen(
         name = "B",
         buttonName = "to Screen C",
         onNext = {
             currentScreen = Screen.C
         }
    )
}

So now, the responsibility of setting up the state is on the current screen and not the screen before it like before.

Is this better?

After some testing: Apparently moving from Screen A -> Screen B -> Screen C makes it that onActive { } gets called the wrong order. So there are states where Screen B have Screen A as its title... Screen C have Screen B as its title and so on...

Just realize even the first approach has flaws as you also have to set the state when going back to a screen.. lets say when doing... Screen A -> Screen B -> Screen C -> press back (Screen B)... Now the state is wrong...

1 Answers
Related