Jetpack Compose is a modern toolkit for building native Android UI. It simplifies UI development with a declarative approach and introduces various state management techniques. One such technique is derivedStateOf, which optimizes state derivations, enhancing performance by minimizing unnecessary recompositions.
What is Derived State?
In Jetpack Compose, derived state refers to a state that is computed or derived from one or more other states. It is essentially a way to create a new state that reacts to changes in the source state(s). This is crucial for transforming and combining states in a performant manner.
Why Use derivedStateOf?
Using derivedStateOf offers several benefits:
- Performance Optimization:
derivedStateOfavoids unnecessary recompositions by only updating the derived state when the source state(s) it depends on change. - Readability and Maintainability: By clearly defining state derivations, the code becomes more readable and easier to maintain.
- Efficiency: Computation only occurs when necessary, preventing expensive operations from being performed repeatedly.
How derivedStateOf Works
derivedStateOf takes a lambda function that performs a computation based on other states. The result of this computation is a state that is automatically updated whenever its dependent states change. However, it does this efficiently by remembering its dependencies and only recomputing when those specific dependencies change.
Implementing derivedStateOf in Jetpack Compose
Here’s how you can use derivedStateOf in Jetpack Compose:
Step 1: Add Dependencies (If Needed)
Ensure you have the necessary Compose dependencies in your build.gradle file:
dependencies {
implementation("androidx.compose.ui:ui:1.5.0") // or newer
implementation("androidx.compose.runtime:runtime:1.5.0") // or newer
implementation("androidx.activity:activity-compose:1.8.0")
}
Step 2: Use derivedStateOf in a Composable Function
Create a composable function that uses derivedStateOf to derive a state from one or more other states.
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.derivedStateOf
import androidx.compose.material.Text
import androidx.compose.material.Button
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.unit.dp
@Composable
fun DerivedStateExample() {
var text1 by remember { mutableStateOf("Hello") }
var text2 by remember { mutableStateOf("World") }
val combinedText by derivedStateOf {
"$text1 $text2"
}
Column(modifier = Modifier.padding(16.dp)) {
Text(text = "Text 1: $text1")
Text(text = "Text 2: $text2")
Text(text = "Combined Text: $combinedText")
Button(onClick = { text1 = "Compose" }) {
Text("Update Text 1")
}
Button(onClick = { text2 = "Android" }) {
Text("Update Text 2")
}
}
}
@Preview(showBackground = true)
@Composable
fun PreviewDerivedStateExample() {
DerivedStateExample()
}
In this example:
text1andtext2are two independent mutable states.combinedTextis a derived state that combinestext1andtext2.- The lambda expression inside
derivedStateOfis only re-executed when eithertext1ortext2changes, preventing unnecessary recompositions.
Advanced Usage
Combining Multiple States
You can derive a state from multiple states, making complex transformations easier to manage.
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.derivedStateOf
import androidx.compose.material.Text
import androidx.compose.material.Button
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.unit.dp
@Composable
fun MultiStateExample() {
var number1 by remember { mutableStateOf(10) }
var number2 by remember { mutableStateOf(5) }
val result by derivedStateOf {
number1 * number2
}
Column(modifier = Modifier.padding(16.dp)) {
Text(text = "Number 1: $number1")
Text(text = "Number 2: $number2")
Text(text = "Result: $result")
Button(onClick = { number1 += 5 }) {
Text("Increment Number 1")
}
Button(onClick = { number2 += 2 }) {
Text("Increment Number 2")
}
}
}
@Preview(showBackground = true)
@Composable
fun PreviewMultiStateExample() {
MultiStateExample()
}
Conditional Derivation
Derive states based on conditions, ensuring the UI responds contextually to different scenarios.
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.derivedStateOf
import androidx.compose.material.Text
import androidx.compose.material.Button
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.unit.dp
@Composable
fun ConditionalDerivationExample() {
var isLoggedIn by remember { mutableStateOf(false) }
val message by derivedStateOf {
if (isLoggedIn) {
"Welcome back!"
} else {
"Please log in."
}
}
Column(modifier = Modifier.padding(16.dp)) {
Text(text = message)
Button(onClick = { isLoggedIn = !isLoggedIn }) {
Text(if (isLoggedIn) "Log Out" else "Log In")
}
}
}
@Preview(showBackground = true)
@Composable
fun PreviewConditionalDerivationExample() {
ConditionalDerivationExample()
}
Real-World Example: Search Functionality
Consider a search feature where you filter a list of items based on a search query. You can use derivedStateOf to efficiently derive the filtered list from the original list and the search query.
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.derivedStateOf
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@Composable
fun SearchExample(items: List) {
var searchQuery by remember { mutableStateOf("") }
val filteredItems by derivedStateOf {
items.filter { it.contains(searchQuery, ignoreCase = true) }
}
Column(modifier = Modifier.padding(16.dp)) {
TextField(
value = searchQuery,
onValueChange = { searchQuery = it },
label = { Text("Search") }
)
LazyColumn {
items(filteredItems) { item ->
Text(text = item, modifier = Modifier.padding(8.dp))
}
}
}
}
@Preview(showBackground = true)
@Composable
fun PreviewSearchExample() {
val items = listOf("Apple", "Banana", "Cherry", "Date", "Fig")
SearchExample(items = items)
}
In this example:
searchQueryis the input from the user.filteredItemsis a derived state that only updates whensearchQueryoritemschanges, making the search efficient.
Best Practices
- Keep Computations Simple: The lambda function inside
derivedStateOfshould be simple and fast to avoid blocking the UI thread. - Minimize Dependencies: Only depend on the states that are absolutely necessary to reduce unnecessary recompositions.
- Use
remember: When referencing objects within the lambda, userememberto avoid creating new objects on every recomposition.
Conclusion
derivedStateOf is a powerful tool in Jetpack Compose for creating derived states efficiently. By understanding how it works and applying it correctly, you can significantly improve the performance and maintainability of your Compose applications. Whether it’s combining multiple states, implementing conditional logic, or optimizing complex transformations, derivedStateOf helps ensure your UI remains responsive and efficient.