Jetpack Compose: Update state of another function's variable

Introduction

In modern Android development, Jetpack Compose has simplified UI design by enabling declarative programming, where UI components respond to changes in state directly. The example code provided showcases how to use Jetpack Compose in Kotlin to create a dynamic UI. Specifically, it demonstrates how to update the state of a variable across composable functions, illustrating the reactivity Compose offers for managing UI states. This code provides a great learning opportunity for understanding state management in Jetpack Compose, particularly how to share and update the state between different composables.

This example uses a simple scaffold layout in Compose, with a button that increments a counter displayed in the top app bar. By following a declarative pattern, it emphasizes the effective use of MutableState to manage UI changes. Let's break down each part of the code to understand how the state is managed, passed, and displayed across different composables.

MainActivity Setup

The code begins with the MainActivity class, which extends AppCompatActivity. In the onCreate method, the setContent function is called, setting the main content of the activity to the GetScaffold composable. This is a typical setup in Jetpack Compose, where setContent is used instead of traditional XML layouts, allowing you to define the UI in a declarative manner. The use of @ExperimentalMaterialApi annotations signifies that certain Material Design components are used, which might be in a beta or experimental phase, ensuring compatibility and functionality in Compose.

The GetScaffold composable serves as the root layout of the UI. Inside GetScaffold, a mutable state favCounter is initialized using remember { mutableStateOf(0) }. This mutable state, of type MutableState<Int>, holds the value of the counter, starting at 0, and can be shared across multiple composables to keep the UI consistent. This setup enables favCounter to be passed to child composables, where it can be modified, triggering recompositions wherever it’s used in the UI.

TopAppBarContent Composable

The TopAppBarContent composable defines the top app bar, where favCounter is displayed. This function takes favCounter as a parameter, allowing it to access the current count value. Within this function, a TopAppBar component is used to create the app bar layout. Inside the actions parameter, a Box with specific styling is created to display the value of favCounter. The Box is centered, with rounded corners and a light background color. The actual counter value is displayed in bold, with the text derived from favCounter.value.

By passing favCounter as a parameter, TopAppBarContent directly accesses and reflects any changes to the counter. When favCounter is updated elsewhere, Compose automatically triggers a re-composition of TopAppBarContent, ensuring that the displayed counter is always up-to-date. This feature highlights one of Jetpack Compose’s core principles: reactivity based on state changes.

MainContent Composable

The main interactive part of the UI is defined in the MainContent composable. Similar to TopAppBarContent, MainContent receives favCounter as a parameter. This composable centers a button and text within the screen using a Box layout with padding and alignment settings for the Column child. The text simply informs the user about the purpose of this screen, and the button provides interactivity by allowing users to increment the counter.

The button’s onClick lambda function contains favCounter.value++, which increments the counter by 1. Since favCounter is mutable, any updates to favCounter.value cause re-composition in any composables that use it, including TopAppBarContent. This setup illustrates how changes in one part of the UI (the button click) can propagate updates throughout the app, effectively demonstrating state sharing between composables.

Preview and Testing

The final section includes a @Preview-annotated composable function, ComposablePreview, which would allow developers to preview the UI within Android Studio. While GetScaffold() is commented out in the preview, uncommenting it would allow developers to see how the entire layout appears without needing to run the app on an emulator or physical device. Previews are an essential feature in Jetpack Compose, making it easier to design and test UI layouts in real time.

Summary

This example highlights effective state management in Jetpack Compose, using a shared mutable state to control and synchronize UI elements across multiple composables. The use of MutableState provides reactivity, ensuring that UI updates occur wherever the state is referenced, all without complex callback structures or listeners. By keeping favCounter as a shared state, this setup ensures a clean, responsive, and efficient UI design.

This approach is particularly powerful in Android development as it reduces boilerplate code and makes the UI more intuitive and readable. By following this pattern, developers can build reactive, maintainable UIs in Jetpack Compose, leveraging Kotlin’s modern features for clean and concise state management.


MainActivity.kt

package com.cfsuman.jetpackcompose

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp


class MainActivity : AppCompatActivity() {
    @ExperimentalMaterialApi
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            GetScaffold()
        }
    }


    @ExperimentalMaterialApi
    @Composable
    fun GetScaffold(){
        var favCounter = remember {mutableStateOf(0)}

        Scaffold(
            topBar = { TopAppBarContent(favCounter) },
            content = {MainContent(favCounter)},
            backgroundColor = Color(0xFFEDEAE0)
        )
    }


    @ExperimentalMaterialApi
    @Composable
    fun TopAppBarContent(favCounter: MutableState<Int>) {
        TopAppBar(
            title = { Text(text = "Compose - Update state")},
            backgroundColor = Color(0xFFC0E8D5),
            actions = {
                Box(modifier = Modifier
                    .size(36.dp)
                    .clip(CircleShape)
                    .background(Color(0xFFF0FFFF))
                    .padding(8.dp),
                    contentAlignment = Alignment.Center
                ) {
                    Text(
                        text = "${favCounter.value}",
                        fontWeight = FontWeight.Bold
                    )
                }
                Spacer(modifier = Modifier.requiredWidth(12.dp))
            }
        )
    }


    @Composable
    fun MainContent(favCounter: MutableState<Int>){
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ){
            Column(
                modifier = Modifier
                    .wrapContentSize(Alignment.Center)
                    .padding(24.dp),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.spacedBy(12.dp)
            ) {
                Text(
                    text = "Update state of another function variable",
                    style = MaterialTheme.typography.h6
                )
                Button(
                    onClick = {
                        favCounter.value++
                    }
                ) {
                    Text(text ="Update State")
                }
            }
        }
    }


    @Preview
    @Composable
    fun ComposablePreview(){
        //GetScaffold()
    }
}
More android jetpack compose tutorials