The state of an application is held by its variables, lists and objects. The values of these change as the application runs. Often we want this change of state to be reflected in the UI: text to update, values to alter, etc.
State Variables
To make this easy, Compose MP provides state varaibles that are 'observable' which means that they are monitored for changes - Any UI elements that are linked to them are automatically updated when the variable values change.
Using Compose State Variables
Where to Declare State Variables
Declare state variables inside the @Composable function that will use it. This will often be App(), but it could be within a smaller element within the App.
How to Declare State Variables
State variables are created as:
MutableStateOf<Type>
For single Strings, Ints, etc.
MutableStateListOf<Type>
For lists (arrays) of objects
To create a single state variable use the by keyword. For a list state varaible, use =. Wrap the declaration in a remember {...} block to preserve the value when the UI updates...
Example State Variable Declarations
@ComposablefunApp(){var message by remember{mutableStateOf("Welcome!")}var names = remember{
mutableStateListOf<String>()}// Window content}
Text Showing State
Please wait...Setting up systemConnecting to serverSystem ready
The Text element can be used to display text which can be styled in various ways.
Text can be linked to an observable, mutable object created via mutableStateOf(...) meaning that any chnges to the object will caude the UI to update accordingly.
funmain()=singleWindowApplication(
title ="System Status"){App()}@ComposablefunApp(){// Value will be updated elsewhere in the codevar status by remember{mutableStateOf("Please wait...")}
Column {Text(status)}}
Responding to Buttons
Pick an option...You picked AYou picked CYou picked B
Button elements can trigger a change in internal state, which could then be reflected in Text elements.
Buttons require an onClick parameter with the code that is run when the Button is clicked. Here is where the internal state can be updated.
funmain()=singleWindowApplication(
title ="Pick an Option"){App()}@ComposablefunApp(){var message by remember {mutableStateOf("Pick an option...")}
Column {Text(message)
Row {Button(onClick ={
message ="You picked A"}){Text("A")}Button(onClick ={
message ="You picked B"}){Text("B")}Button(onClick ={
message ="You picked C"}){Text("C")}}}}
Text Changing State
Enter name...Welcome, Jimmy!
The OutlinedTextField element can be used to get text input from the user.
TextFields require an onValueChange parameter with the code to be run when text is typed.
A TextField will usually update an observable, mutable object created via mutableStateOf(...), so the value parameter is set to the object and the onValueChange code updates the object.
funmain()=singleWindowApplication(
title ="Who Are You?",){App()}@ComposablefunApp(){var name by remember {mutableStateOf("")}var welcome by remember {mutableStateOf("Enter name...")}
Column {Text(welcome)OutlinedTextField(
label ={Text("Name")},
value = name,
onValueChange ={ name = it })Button(
onClick ={
welcome ="Welcome, $name!"}){Text("Save")}}}
Rather than use a MutableListOf<String> to store a list of Strings, instead use a MutableStateListOf<String>. This is an observable list, meaning that any changes to it will cause the UI to update accordingly.
A loop is used to show UI elements for the list.
funmain()=singleWindowApplication(
title ="Employee List"){App()}@ComposablefunApp(){// This list will be updated elsewhere in the codeval employees = mutableStateListOf<String>()
Column {Text("Employees")
Column {// UI elements for each list itemfor(employee in employees){Text(employee)}}}}
When a list of items is required that is more than just simple text (e.g. it might have multiple data fields, action buttons, special styling, etc.) it is best to create a separate, custom @Composable element for this.
This keeps your code much more modular, readable and compact.
val users = mutableStateListOf<User>()funmain()=singleWindowApplication(
title ="User List"){App()}@ComposablefunApp(){Column(){Text("Users")
Column {for(user in users){UserRow(user)}}}}@ComposablefunUserRow(user: User)=Row(
modifier = Modifier.border(2.dp,if(user.admin) Color.Red else Color.Gray
)){Text(user.id)Text(user.name)Button(onClick ={...}){Text("🖉")}Button(onClick ={...}){Text("✖")}}
Modifying List Items & Updating the UI
You need to buy...Apples106-Pack Craft Beer
1221
Toilet Cleaner8
When we use MutableStateListOf<Type>, the list observable so the UI will react to any changes to it, such as adding or removing items.
However, if individual items in the list are modified, the UI will not react. This is because the UI is not 'observing' the individual objects, but only the list as a whole.
The easist way to make the UI react to individual object updates is to replace the object with an updated copy using the data class .copy() function. This modifies the containing list, so the UI reacts.
val shopping = mutableStateListOf<Item>()funmain()=singleWindowApplication(
title ="Shopping List"){App()}@ComposablefunApp(){Column(){Text("You need to buy...")
Column {for(item in shopping){ItemRow(item)}}}}@ComposablefunItemRow(item: Item)=Row(){Text(item.name)Text(item.count)Button(
onClick ={val index = shopping.indexOf(item)val newCount = item.count +1
shopping[index]= item.copy(count = newCount)}){Text("+")}Button(
onClick ={val index = shopping.indexOf(item)val newCount = item.count -1
shopping[index]= item.copy(count = newCount)}){Text("-")}}
Working with Dates
Enter your date of birth... You were born on 25/09/1978 so you are 45 years old
An OutlinedTextField element can be used to get a date from the user. However, internally, you would want to store the date as a LocalDate object, so that it can make use of the many features of this class (e.g. date comparisons, offsets, etc.).
Since the text field works with Strings, you need to convert the LocalDate to/from a String.
For this you need to define a formatter with the date format you plan to use (e.g. 30/06/2021, 30-06-2021, 30 Jun 2021, etc.)
// Define a date formatter for converting between string / dateval formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")funmain()=singleWindowApplication(
title ="Age Calculator",){App()}@ComposablefunApp(){// A LocalDate to store the data (could be part of a class, etc.)var dob = LocalDate.now()var message by remember {mutableStateOf("Enter your date of birth...")}var dobText by remember {mutableStateOf("")}Column(){Text(message)Row(){OutlinedTextField(
label ={Text("DoB (dd/mm/yyyy)")},
value = dobText,
onValueChange ={
dobText = it
})Button(
onClick ={// Extract the DOB from the text via the formatter
dob = LocalDate.parse(dobText, formatter)// Work out the age using LocalDate class functionsval today = LocalDate.now()val age = dob.until(today)// Show DOB as a string via the formatter
message ="You were born on "+ dob.format(formatter)
message +="\n"
message +="so you are ${age.years} years old"}){Text("Calculate")}}}}
Note that these examples have spacing added around elements to help show how the layouts work. The code snippets don't apply the same spacing.