2. Resources
-
Jetpack Compose Tutorial (Simple UI)
-
Jetpack Compose (pathway)
3. Bascis
-
We begin with "Tutorial: Jetpack Compose basics" in the pathway.
-
The @Composable annotation is necessary just for functions which emit UI or call other composable functions. They can call both regular and other composable functions. If a function doesn’t meet these requirements, it shouldn’t be annotated with @Composable.
3.1. Container Functions
-
To make a generic container, create a Composable function that takes as a parameter a Composable function (here called
content
) which returnsUnit
. You returnUnit
because, as you might have noticed, Composable functions don’t return UI components, they emit them. That’s why they must returnUnit
:@Composable fun MyApp(content: @Composable () -> Unit) { BasicsCodelabTheme { Surface(color = Color.Yellow) { content() } } }
-
Watch out for the extra parentheses in @Composable() when using a Composable function as a parameter. Since the annotation is applied on a function, they’re needed!
fun MyApp(content: @Composable () -> Unit) { ... }
-
Inside your function, you define all of the shared configuration you want your container to provide and then invoke the passed children Composable. In this case, you want to apply a
MaterialTheme
and a yellow surface, and then callcontent()
.
3.2. Column
-
To place items in a vertical sequence, use the
Column
Composable function (similar to a verticalLinearLayout
).import androidx.compose.foundation.layout.Column import androidx.compose.material.Divider ... @Composable fun MyScreenContent() { Column { Greeting("Android") Divider(color = Color.Black) Greeting("there") } }
-
Divider is a provided composable function that creates a horizontal divider.
3.3. Compose and Kotlin
-
Compose functions can be called like any other function in Kotlin. This makes building UIs really powerful since you can add statements to influence how the UI will be displayed.
-
For example, you can use a for loop to add elements to the MyScreenContent Column:
@Composable fun MyScreenContent(names: List<String> = listOf("Android", "there")) { Column { for (name in names) { Greeting(name = name) Divider(color = Color.Black) } } }
4. State in Compose
-
Reacting to state changes is at the very heart of Compose. Compose apps transform data into UI by calling Composable functions. If your data changes, you recall these functions with the new data, creating an updated UI.
Compose offers tools for observing changes in your app’s data, which will automatically recall your functions - this is called recomposing. |
-
Compose also looks at what data is needed by an individual composable so that it only needs to recompose components whose data has changed and can skip composing those that are not affected.
-
Since Button reads count.value, Button will be recomposed whenever it changes and will display the new value of count.
-
You now can add a Counter to your screen:
@Composable fun MyScreenContent(names: List<String> = listOf("Android", "there")) { Column { for (name in names) { Greeting(name = name) Divider(color = Color.Black) } Divider(color = Color.Transparent, thickness = 32.dp) Counter() } }
4.1. Source of truth
4.1.1. Excursus: String templates
-
String literals may contain template expressions - pieces of code that are evaluated and whose results are concatenated into the string. A template expression starts with a dollar sign ($) and consists of either a name:
val i = 10 println("i = $i") // prints "i = 10"
or an expression in curly braces:
val s = "abc" println("$s.length is ${s.length}") // prints "abc.length is 3"
In Composable functions, state that can be useful to calling functions should be exposed because it’s the only way it can be consumed or controlled—this process is called state hoisting. |
-
State hoisting is the way to make internal state controllable by the function that called it. You do so by exposing the state through a parameter of the controlled composable function and instantiating it externally from the controlling composable. Making state hoistable avoids duplicating state and introducing bugs, helps reuse composables, and makes composables substantially easier to test. State that is not interesting to a composable caller should be internal.
-
In the example, since consumers of
Counter
can be interested in the state, you can defer it to the caller completely by introducing a (count
,updateCount
) pair as parameters of Counter. In this way,Counter
is hoisting its state:
to hoist … etwas hochfördern |
@Composable
fun MyScreenContent(names: List<String> = listOf("Android", "there")) {
val counterState = remember { mutableStateOf(0) }
Column {
for (name in names) {
Greeting(name = name)
Divider(color = Color.Black)
}
Divider(color = Color.Transparent, thickness = 32.dp)
Counter(
count = counterState.value,
updateCount = { newCount -> counterState.value = newCount }
)
}
}
@Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
Button(onClick = { updateCount(count + 1) }) {
Text("I've been clicked $count times")
}
}