Difficulty Level: 🌟🌟 Intermediate
What You’ll Learn
✅ The difference between remember() and mutableStateOf()
✅ 5 common state management patterns and mistakes
✅ Advanced patterns with derivedStateOf and rememberSaveable
✅ Best practices for complex UI state management
✅ Performance optimization for state-heavy Compose apps
Quick Answer: Use
var state by remember { mutableStateOf(value) }
for most UI state.mutableStateOf()
makes state observable,remember()
preserves it across recompositions. Combine both for reactive, persistent state.
Hey there, Compose explorer! 👋
Let me guess—you’ve been staring at remember()
and mutableStateOf()
for the past hour, trying to figure out why your UI state keeps disappearing or never updates, and you’re starting to wonder if Jetpack Compose was designed by someone who enjoys making developers suffer? I feel you completely! 😅
First, let me reassure you of something important: you’re not struggling with this because you’re not smart enough or not cut out for Android development. State management in Jetpack Compose genuinely is one of the most confusing topics for developers, regardless of their background. I’ve seen seasoned Android developers with years of View system experience get completely tangled up in Compose state patterns.
💡 Gentle Reminder: The fact that you’re here, trying to understand the proper way to handle state instead of just copy-pasting code until it works, shows excellent developer instincts. This confusion is temporary—understanding is permanent.
I remember my first week with Compose state management. I must have written and rewritten the same counter component fifty different ways, each time convinced I had finally “got it,” only to have my state mysteriously reset or refuse to update. It was one of those humbling experiences that every developer goes through when learning something truly new.
After helping dozens of developers transition to Compose and debugging countless state-related bugs (including my own!), I’ve identified the most common confusion: not understanding when and how to combine remember()
with mutableStateOf()
. This confusion leads to lost state, unnecessary recompositions, and UI bugs that are genuinely hard to track down.
But here’s the great news: once you understand the relationship between these two functions, state management in Compose becomes intuitive and even enjoyable!
Ready to transform your Compose state confusion into confidence? Let’s clear this up with practical examples that actually make sense!
The Problem: State That Disappears or Never Updates
New Compose developers often write code like this and wonder why their UI doesn’t work correctly:
// ❌ Bad: Common state management mistakes
@Composable
private fun CounterScreen() {
// Mistake #1: No remember - state resets on every recomposition
var count = mutableStateOf(0)
Column {
Text("Count: ${count.value}")
Button(onClick = { count.value++ }) {
Text("Increment")
}
}
}
@Composable
private fun UserProfileScreen() {
// Mistake #2: remember without mutableStateOf - state never changes
val userName = remember { "John Doe" }
Column {
Text("User: $userName")
Button(onClick = {
// This does nothing! userName can't be changed
userName = "Jane Doe" // ❌ Compilation error
}) {
Text("Change Name")
}
}
}
@Composable
private fun TodoScreen() {
// Mistake #3: Wrong delegation pattern
val todos = remember { mutableStateOf(listOf<String>()) }
Column {
Text("Todos: ${todos.value.size}")
Button(onClick = {
// Verbose and error-prone
todos.value = todos.value + "New Todo"
}) {
Text("Add Todo")
}
}
}
These mistakes cause state to either reset unexpectedly or never update at all, creating frustrating bugs.
Understanding the Building Blocks
What is mutableStateOf()
?
mutableStateOf()
creates a state holder that Compose can observe for changes:
// Creates observable state
val state = mutableStateOf(0)
// Reading the value
val currentValue = state.value
// Writing the value (triggers recomposition)
state.value = 5
What is remember()
?
remember()
preserves values across recompositions:
// Without remember - value recreated every recomposition
val expensiveValue = calculateExpensiveValue() // ❌ Recalculated each time
// With remember - value preserved across recompositions
val expensiveValue = remember { calculateExpensiveValue() } // ✅ Calculated once
The Key Insight: They Solve Different Problems
mutableStateOf()
: Makes Compose observe changes (reactivity)remember()
: Preserves values across recompositions (persistence)
For UI state, you usually need both!
Correct State Patterns
Pattern 1: Basic State with remember
+ mutableStateOf
// ✅ Good: Proper state management
@Composable
private fun CounterScreen() {
// Combines both: preserved AND observable
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) { // Clean syntax with 'by' delegate
Text("Increment")
}
Button(onClick = { count-- }) {
Text("Decrement")
}
}
}
Why this works:
mutableStateOf(0)
creates observable stateremember { }
preserves it across recompositionsby
delegate provides clean syntax
Pattern 2: Complex State Objects
// ✅ Good: Managing complex state
private data class UserProfile(
val name: String = "",
val email: String = "",
val age: Int = 0,
val isVerified: Boolean = false
)
@Composable
private fun UserProfileForm() {
var profile by remember {
mutableStateOf(UserProfile())
}
Column {
OutlinedTextField(
value = profile.name,
onValueChange = { newName ->
profile = profile.copy(name = newName) // Immutable updates
},
label = { Text("Name") }
)
OutlinedTextField(
value = profile.email,
onValueChange = { newEmail ->
profile = profile.copy(email = newEmail)
},
label = { Text("Email") }
)
Row {
Checkbox(
checked = profile.isVerified,
onCheckedChange = { isChecked ->
profile = profile.copy(isVerified = isChecked)
}
)
Text("Verified")
}
Button(
onClick = { saveProfile(profile) },
enabled = profile.name.isNotBlank() && profile.email.isNotBlank()
) {
Text("Save Profile")
}
}
}
Pattern 3: List State Management
// ✅ Good: Managing list state
@Composable
private fun TodoListScreen() {
var todos by remember { mutableStateOf(listOf<String>()) }
var newTodoText by remember { mutableStateOf("") }
Column {
Row {
OutlinedTextField(
value = newTodoText,
onValueChange = { newTodoText = it },
modifier = Modifier.weight(1f),
label = { Text("New Todo") }
)
Button(
onClick = {
if (newTodoText.isNotBlank()) {
todos = todos + newTodoText // Immutable append
newTodoText = "" // Clear input
}
}
) {
Text("Add")
}
}
LazyColumn {
itemsIndexed(todos) { index, todo ->
TodoItem(
text = todo,
onDelete = {
todos = todos.filterIndexed { i, _ -> i != index }
}
)
}
}
}
}
@Composable
private fun TodoItem(
text: String,
onDelete: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text)
IconButton(onClick = onDelete) {
Icon(Icons.Default.Delete, contentDescription = "Delete")
}
}
}
Common Mistakes and Solutions
Mistake 1: Forgetting remember
// ❌ Bad: State resets on every recomposition
@Composable
private fun SearchScreen() {
var searchQuery = mutableStateOf("") // Lost on recomposition!
// UI code...
}
// ✅ Good: State preserved across recompositions
@Composable
private fun SearchScreen() {
var searchQuery by remember { mutableStateOf("") }
// UI code...
}
Mistake 2: Using remember
Without State
// ❌ Bad: Value can't change, no reactivity
@Composable
private fun GreetingScreen() {
val greeting = remember { "Hello World" } // Static value
Text(greeting) // Will never change
}
// ✅ Good: Use remember for expensive calculations, not simple values
@Composable
private fun GreetingScreen() {
val currentTime = remember { System.currentTimeMillis() }
val formattedTime = remember(currentTime) {
SimpleDateFormat("HH:mm:ss").format(Date(currentTime))
}
Text("App started at: $formattedTime")
}
Mistake 3: Mutating State Objects Directly
// ❌ Bad: Direct mutation doesn't trigger recomposition
@Composable
private fun UserListScreen() {
val users = remember { mutableStateOf(mutableListOf<User>()) }
Button(onClick = {
users.value.add(User("New User")) // ❌ Compose won't detect this change
}) {
Text("Add User")
}
}
// ✅ Good: Create new instances for state updates
@Composable
private fun UserListScreen() {
var users by remember { mutableStateOf(listOf<User>()) }
Button(onClick = {
users = users + User("New User") // ✅ New list triggers recomposition
}) {
Text("Add User")
}
}
Advanced State Patterns
Pattern 1: Computed State with derivedStateOf
// ✅ Good: Efficient computed state
@Composable
private fun ShoppingCartScreen() {
var cartItems by remember { mutableStateOf(listOf<CartItem>()) }
// Only recomputes when cartItems changes
val totalPrice by remember(cartItems) {
derivedStateOf {
cartItems.sumOf { it.price * it.quantity }
}
}
val itemCount by remember(cartItems) {
derivedStateOf {
cartItems.sumOf { it.quantity }
}
}
Column {
Text("Items: $itemCount")
Text("Total: $${totalPrice}")
LazyColumn {
items(cartItems) { item ->
CartItemRow(item) { updatedItem ->
cartItems = cartItems.map {
if (it.id == updatedItem.id) updatedItem else it
}
}
}
}
}
}
Pattern 2: State with Keys for Conditional Remember
// ✅ Good: State that depends on external data
@Composable
private fun UserProfileScreen(userId: String) {
// State recreated when userId changes
var profile by remember(userId) {
mutableStateOf(UserProfile())
}
var isLoading by remember(userId) {
mutableStateOf(true)
}
// Load profile when userId changes
LaunchedEffect(userId) {
isLoading = true
profile = loadUserProfile(userId)
isLoading = false
}
if (isLoading) {
CircularProgressIndicator()
} else {
ProfileContent(profile) { updatedProfile ->
profile = updatedProfile
}
}
}
Pattern 3: State Persistence with rememberSaveable
// ✅ Good: State survives configuration changes
@Composable
private fun FormScreen() {
// Survives screen rotation, process death, etc.
var formData by rememberSaveable {
mutableStateOf(FormData())
}
var isSubmitting by rememberSaveable {
mutableStateOf(false)
}
Column {
OutlinedTextField(
value = formData.name,
onValueChange = { formData = formData.copy(name = it) },
label = { Text("Name") }
)
OutlinedTextField(
value = formData.email,
onValueChange = { formData = formData.copy(email = it) },
label = { Text("Email") }
)
Button(
onClick = {
isSubmitting = true
submitForm(formData) { success ->
isSubmitting = false
if (success) {
formData = FormData() // Reset form
}
}
},
enabled = !isSubmitting
) {
if (isSubmitting) {
CircularProgressIndicator(modifier = Modifier.size(16.dp))
} else {
Text("Submit")
}
}
}
}
Best Practices Summary
✅ Do:
- Combine
remember
+mutableStateOf
for UI state - Use
by
delegate for cleaner syntax - Create new objects for state updates (immutability)
- Use
derivedStateOf
for computed state - Use
rememberSaveable
for important user data - Key
remember
when state depends on external data
❌ Don’t:
- Use
mutableStateOf
withoutremember
(state will reset) - Use
remember
for simple static values (unnecessary) - Mutate state objects directly (won’t trigger recomposition)
- Forget to handle loading states in async operations
- Over-use state (prefer stateless composables when possible)
Quick Reference Guide
Pattern | Use Case | Example |
---|---|---|
var state by remember { mutableStateOf(value) } | Basic mutable state | Counters, toggles, form inputs |
remember { expensiveCalculation() } | Expensive computations | Complex object creation |
remember(key) { mutableStateOf(value) } | State dependent on external data | User profiles, search results |
derivedStateOf { computation } | Computed state | Totals, filtered lists |
rememberSaveable { mutableStateOf(value) } | Persistent state | Form data, user preferences |
Frequently Asked Questions
Q: What’s the difference between remember and mutableStateOf?
A: mutableStateOf()
creates observable state that triggers recomposition. remember()
preserves values across recompositions. You usually need both together.
Q: When should I use rememberSaveable instead of remember?
A: Use rememberSaveable
for important user data that should survive configuration changes (screen rotation) and process death (like form inputs).
Q: Why does my state reset when I navigate away and back?
A: You’re likely missing remember
. Without it, state gets recreated on every recomposition. Use remember { mutableStateOf() }
to persist state.
Q: Should I use derivedStateOf for computed values?
A: Yes, for expensive computations that depend on other state. derivedStateOf
only recalculates when its dependencies change, improving performance.
Q: Can I use mutableStateOf without remember?
A: Technically yes, but your state will reset on every recomposition, making your UI non-functional. Always combine them for UI state.
Conclusion
Understanding the relationship between remember()
and mutableStateOf()
is crucial for effective Compose development. Remember this simple rule: mutableStateOf()
makes state observable, remember()
makes it persistent. This becomes even more important when working with complex UIs where proper @Composable annotations are essential and Kotlin null safety prevents state-related crashes.
Most UI state needs both: you want Compose to react to changes AND preserve state across recompositions. The by
delegate syntax makes this pattern clean and readable.
Start with basic var state by remember { mutableStateOf(initialValue) }
for most cases, then learn the advanced patterns as your apps become more complex. Master the fundamentals of Kotlin’s null safety alongside Compose state management for the most robust applications. If you’re transitioning from Unity development, these state patterns will feel familiar yet more powerful. With these fundamentals solid, you’ll write bug-free Compose UIs with confidence.
Need help building robust Android apps with Jetpack Compose? Contact Angry Shark Studio for expert Android development services, or explore our mobile development portfolio to see how we’ve built performant, state-driven Compose applications.
Related Reading:

About Angry Shark Studio
Angry Shark Studio is a professional Unity AR/VR development studio specializing in mobile multiplatform applications and AI solutions. Our team includes Unity Certified Expert Programmers with extensive experience in AR/VR development.
Related Articles
More Articles
Explore more insights on Unity AR/VR development, mobile apps, and emerging technologies.
View All Articles