JetPack Compose Basic overview
It’s never too late to begin your journey in the world of programming😊.
Jetpack Compose is a modern declarative UI toolkit for Android.
- It Allows Devs to build user interfaces using a more reactive and declarative style of programming, which simplifies the process of creating UIs.
- It’s designed to offer more flexibility and easy development compared to the traditional imperative UI frameworks.
- With Compose, UI components are built using a composable function approach, making it easier to create dynamic and complex user interfaces.
The best thing which I found in compose documentation is:
We always have challenges while we're regenerating the entire screen in XML and it's quite expensive, in terms of time, computing power and battery usage.
After migrating into compose, it always chooses which parts of the UI need to be regenerated at the given time, this process is called recomposition.
Let's discuss a bit about recomposition:
Recomposition is the process of calling your composable functions again when inputs change.
When Compose recomposes based on new inputs, it only calls the functions or lambdas that might have changed, and skips the rest. By skipping all functions or lambdas that don’t have changed parameters, Compose can recompose efficiently.
Key considerations for utilizing Compose effectively
- Composable functions can be executed in any order.
- Composable functions can be executed in parallel.
- Recomposition skips as many composable functions and lambdas as possible.
- The recomposition is optimistic and may be cancelled.
- A composable function might be run quite frequently, as often as every frame of an animation.
let's discuss step by step:
Composable functions can execute in any order🤔
If a composable function contains calls to other composable functions, those functions might run in any order.
let's take an example here
@Composable
fun ButtonRow() {
MyFancyNavigation {
StartScreen()
MiddleScreen()
EndScreen()
}
}
Compose has the option of recognizing that some UI elements are higher priority than others, and drawing them first.
Composable functions can run in parallel🧐
example:
@Composable
fun ListComposable(myList: List<String>) {
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Column {
for (item in myList) {
Text("Item: $item")
}
}
Text("Count: ${myList.size}")
}
}
Compose can optimize recomposition by running composable functions in parallel.
If a composable function calls a function on a ViewModel
, Compose might call that function from several threads at the same time.
To ensure the application behaves correctly, all composable functions should have no side effects. Instead, trigger side-effects from callbacks such as onClick
that always execute on the UI thread.
side-effect🤔
In Jetpack Compose, the UI is built using functions that describe the UI based on their input parameters.
These functions should not perform side effects directly. Instead, side effects, like fetching data, updating a database, or initiating network requests, should be triggered from designated points, like event handlers (e.g., onClick
for a button) or other callbacks.
How to avoid side effects?
@Composable
fun DisplayData(data: String) {
// This is a Composable function
Text(text = data)
// Performing a side-effect - displaying a toast message
LaunchedEffect(Unit) {
// This code block will execute as a side-effect
Toast.makeText(LocalContext.current, "Data displayed", Toast.LENGTH_SHORT).show()
}
}
Jetpack Compose recommends keeping side effects separate from the Composable functions and triggering them from appropriate contexts (like LaunchedEffect
, SideEffect
, or callback functions such as onClick
).
This separation maintains a clean and predictable rendering process while handling side effects in a controlled manner.
In a simpler way:
Separating side effects from the composable functions themselves ensures a clear separation of concerns, promotes a more predictable UI behaviour, and facilitates easier testing and maintenance of the UI code.
Recomposition skips as much as possible🥱
In Jetpack Compose, recomposition is the process by which the UI is updated based on changes to the state or data used in composable.
Recomposition is efficient because it aims to skip unnecessary updates, optimizing the performance of the UI rendering.
/**
* Display a list of names the user can click with a header
*/
@Composable
fun NamePicker(
header: String,
names: List<String>,
onNameClicked: (String) -> Unit
) {
Column {
// this will recompose when [header] changes, but not when [names] changes
Text(header, style = MaterialTheme.typography.bodyLarge)
Divider()
// LazyColumn is the Compose version of a RecyclerView.
// The lambda passed to items() is similar to a RecyclerView.ViewHolder.
LazyColumn {
items(names) { name ->
// When an item's [name] updates, the adapter for that item
// will recompose. This will not recompose when [header] changes
NamePickerItem(name, onNameClicked)
}
}
}
}
/**
* Display a single name the user can click.
*/
@Composable
private fun NamePickerItem(name: String, onClicked: (String) -> Unit) {
Text(name, Modifier.clickable(onClick = { onClicked(name) }))
}
Jetpack Compose employs a diffing algorithm to compare the current state of the UI with the new state and recompose only the parts that have changed. It skips unnecessary recompositions by identifying which parts of the UI are unaffected by the state changes.
The recomposition is optimistic and may be cancelled.🤔
Recomposition starts whenever Compose thinks that the parameters of a composable might have changed. Recomposition is optimistic, which means compose expects to finish recomposition before the parameters change again.
If a parameter does change before the recomposition finishes, Compose might cancel the recomposition and restart it with the new parameter.
When the recomposition is cancelled, Compose discards the UI tree from the recomposition. If you have any side effects that depend on the UI being displayed, the side effects will be applied even if the composition is cancelled. This can lead to inconsistent app state.
Ensure that all composable functions and lambdas are idempotent and side-effect-free to handle optimistic recomposition.
Composable functions might run quite frequently😇
In some cases, a composable function might run for every frame of a UI animation. If the function performs expensive operations, like reading from device storage, the function can cause UI jank.
If your composable function needs data, it should define parameters for the data. You can then move expensive work to another thread, outside of composition, and pass the data to Compose using mutableStateOf
or LiveData
.
let's discuss more in the next article….