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:
derivedStateOf
avoids 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:
text1
andtext2
are two independent mutable states.combinedText
is a derived state that combinestext1
andtext2
.- The lambda expression inside
derivedStateOf
is only re-executed when eithertext1
ortext2
changes, 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:
searchQuery
is the input from the user.filteredItems
is a derived state that only updates whensearchQuery
oritems
changes, making the search efficient.
Best Practices
- Keep Computations Simple: The lambda function inside
derivedStateOf
should 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, useremember
to 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.