Manage state in Jetpack Compose

Viewed 1709

In my viewModel I have "state" for every single screen. e.g.

class MainState(val commonState: CommonState) {
    val text = MutableStateFlow("text")
}

I pass viewModel to my JetpackCompose screen.

@Composable
fun MainScreen(text: String, viewModel: HomeViewModel) {
    val textState = remember { mutableStateOf(TextFieldValue()) }
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = viewModel.state.mainState.text.value,
            color = Color.Blue,
            fontSize = 40.sp
        )

        Button(
            onClick = { viewModel.state.mainState.text.value = "New text" },
            colors = ButtonDefaults.buttonColors(
                backgroundColor = Color.Green
            ),
            modifier = Modifier.padding(16.dp)

        ) {
            Text(text)
        }

        TextField(
            value = textState.value,
            onValueChange = { textState.value = it },
            label = { Text("Input text") }
        )
        
    }
}

When I click button I change value of state and I expect UI will update but it does not. I have to click on TextField and then text in TextView updates.

Any suggestion why UI does not update automatically?

That's how I pass components and start whole screen in startActivity;

class HomeActivity : ComponentActivity() {

    private val viewModel by viewModel<HomeViewModel>()
    private val homeState: HomeState get() = viewModel.state

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            RateMeAppTheme {
                ContentScreen(viewModel, homeState)
            }
        }

    }

}
1 Answers

In this simple case u should use mutableStateOf("text") in class MainState instead of mutableStateFlow

class MainState(val commonState: CommonState) {
    val text = mutableStateOf("text")
}

Using MutableStateFlow

To use MutableStateFlow (which is not required in the current scenario) , we need to collect the flow.

Like the following:-

val state = viewModel.mainState.text.collectAsState() // we can collect a stateflow as state in a composable function

Then we can use the observed state value in the Text using:-

Text(text = state.value, ..)

Finally your composable function should look like:-

@Composable
fun MainScreen(text: String, viewModel: HomeViewModel) {
    val textState = remember { mutableStateOf(TextFieldValue()) }
    val state = viewModel.mainState.text.collectAsState()
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = state.value,
            color = Color.Blue,
            fontSize = 40.sp
        )

        Button(
            onClick = { viewModel.mainState.text.value = "New text" },
            colors = ButtonDefaults.buttonColors(
                backgroundColor = Color.Green
            ),
            modifier = Modifier.padding(16.dp)

        ) {
            Text(text)
        }

        TextField(
            value = textState.value,
            onValueChange = { textState.value = it },
            label = { Text("Input text") }
        )

    }
}
Related