Question
Fixing observeAsState Delegation Errors in Jetpack Compose Kotlin
Question
In Jetpack Compose, I am trying to read a LiveData value using observeAsState(), but I get this error:
Type 'State<List<User>?>' has no method 'getValue(Nothing?, KProperty<*>)' and thus it cannot serve as a delegate
Here is my code:
@Composable
fun UserScreen(userViewModel: UserViewModel) {
val items: List<User> by userViewModel.fetchUserList.observeAsState()
UserList(userList = items)
}
And my ViewModel:
class UserViewModel : ViewModel() {
private val dataSource = UserDataSource()
val fetchUserList = liveData {
emit(dataSource.dummyUserList)
}
}
Why does this happen, and what is the correct way to use observeAsState() with LiveData in Jetpack Compose?
Short Answer
By the end of this page, you will understand how observeAsState() converts LiveData into Compose State, why the by syntax sometimes fails, how nullability affects the result, and how to fix the delegate error in a clean and idiomatic way.
Concept
In Jetpack Compose, observeAsState() is used to bridge older Android LiveData into the Compose state system.
When you call:
val itemsState = userViewModel.fetchUserList.observeAsState()
Compose gives you a State<T?> object. That means:
- it is a wrapper around a value
- the value is read through
.value - the value may be
nulluntilLiveDataemits something
The by keyword is Kotlin property delegation syntax. It lets you write this:
val items by userViewModel.fetchUserList.observeAsState()
instead of this:
val items = userViewModel.fetchUserList.observeAsState().value
However, delegation only works if the required operator function is available. In Compose, this usually comes from the import:
import androidx.compose.runtime.getValue
Without that import, Kotlin does not know how to delegate a object to a local property, so it reports the error.
Mental Model
Think of LiveData as a mailbox that receives updates over time.
observeAsState() turns that mailbox into a Compose-readable container.
- The mailbox is
LiveData - The container is
State<T> - The actual letter inside is
value
Using .value is like opening the container directly.
Using by is like asking Kotlin to automatically open the container for you.
But for Kotlin to do that automatically, it needs the correct delegation tool imported. If that tool is missing, Kotlin says the container cannot serve as a delegate.
Syntax and Examples
The core syntax looks like this:
val usersState = viewModel.fetchUserList.observeAsState()
val users = usersState.value
Or with delegation:
import androidx.compose.runtime.getValue
val users by viewModel.fetchUserList.observeAsState()
Example 1: Using .value
@Composable
fun UserScreen(userViewModel: UserViewModel) {
val itemsState = userViewModel.fetchUserList.observeAsState()
val items = itemsState.value ?: emptyList()
UserList(userList = items)
}
This works because:
observeAsState()returnsState<List<User>?>itemsState.valuemay benull?: emptyList()provides a safe fallback
Example 2: Using by with an initial value
Step by Step Execution
Consider this version:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
@Composable
fun UserScreen(userViewModel: UserViewModel) {
val items by userViewModel.fetchUserList.observeAsState(initial = emptyList())
UserList(userList = items)
}
Here is what happens step by step:
UserScreen(...)is called by Compose.userViewModel.fetchUserListis aLiveData<List<User>>.observeAsState(initial = emptyList())starts observing thatLiveData.- Before
LiveDataemits real data, Compose usesemptyList()as the current value. - Because
getValueis imported,bycan unwrap theStateautomatically. itemsis now a regularList<User>value, not aState<List<User>>.
Real World Use Cases
This pattern is used whenever UI reads changing data from a ViewModel.
Common scenarios
- User lists: display users from a repository or database
- API responses: show products, posts, or comments after a network request
- Form state: update the screen when validation messages change
- Settings screens: observe saved preferences and reflect them in UI
- Loading screens: show progress until data arrives
Example: Product list from LiveData
@Composable
fun ProductScreen(viewModel: ProductViewModel) {
val products by viewModel.products.observeAsState(initial = emptyList())
ProductList(products)
}
Example: Nullable state for loading
@Composable
fun ProfileScreen(viewModel: ProfileViewModel) {
val profile by viewModel.profile.observeAsState()
when {
profile == null -> LoadingView()
else -> ProfileCard(profile)
}
}
In real apps, choosing between nullable state and an initial value depends on whether you want to represent loading explicitly.
Real Codebase Usage
In real projects, developers usually use one of these patterns.
1. Provide an initial value for collections
For lists, maps, and sets, an initial empty collection is common.
val users by viewModel.fetchUserList.observeAsState(initial = emptyList())
This avoids repeated null checks in UI code.
2. Use nullable state to represent loading
val user by viewModel.user.observeAsState()
if (user == null) {
LoadingView()
} else {
UserDetails(user)
}
This is useful when null means "not loaded yet."
3. Use guard clauses for clean UI logic
val users by viewModel.fetchUserList.observeAsState()
if (users == null) {
LoadingView()
return
}
UserList(userList = users)
This keeps the main rendering path simple.
4. Prefer UI-friendly state in the ViewModel
In larger codebases, developers often expose state already shaped for the UI, such as:
- empty list instead of null
- sealed UI state like Loading / Success / Error
- domain-specific state objects
Common Mistakes
1. Forgetting the getValue import
Broken code:
val items by viewModel.fetchUserList.observeAsState()
If you use by, you usually need:
import androidx.compose.runtime.getValue
Fix:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
2. Treating nullable state as non-null
Broken code:
val items: List<User> by viewModel.fetchUserList.observeAsState()
Why it is wrong:
observeAsState()withoutinitialreturns a nullable valueitemsshould beList<User>?, or you should provide an initial value
Fix:
Comparisons
| Concept | What it returns | How you read the value | Typical use |
|---|---|---|---|
observeAsState() | State<T?> or State<T> with initial value | .value or by | Read LiveData in Compose |
mutableStateOf() | MutableState<T> | .value or by | Local Compose state |
collectAsState() | State<T> | or |
Cheat Sheet
// Use LiveData in Compose
val state = viewModel.data.observeAsState()
// Read value explicitly
val value = state.value
// Or use delegation
import androidx.compose.runtime.getValue
val value by viewModel.data.observeAsState()
Key rules
observeAsState()convertsLiveData<T>into ComposeState<T?>- Without an initial value, the result is usually nullable
- Use
initial = ...when you want a non-null value immediately - If you use
by, import:androidx.compose.runtime.getValueandroidx.compose.runtime.livedata.observeAsState
Safe patterns
val items by viewModel.fetchUserList.observeAsState(initial = emptyList())
val items = viewModel.fetchUserList.observeAsState().value ?: emptyList()
FAQ
Why does observeAsState() return a nullable value?
Because the LiveData may not have emitted a value yet when the composable first runs.
How do I fix the getValue delegate error in Compose?
Import:
import androidx.compose.runtime.getValue
and make sure you also import observeAsState from the LiveData Compose package.
Should I use .value or by with Compose state?
Both are correct. .value is more explicit. by is shorter and more idiomatic once you understand delegation.
What initial value should I use for a list?
Usually emptyList(), unless your UI needs to distinguish between "loading" and "loaded but empty."
Can I pass LiveData<List<User>> directly to a composable?
You usually should not. Convert it to Compose state inside the composable with observeAsState() and pass plain UI data downward.
Is still okay in Jetpack Compose?
Mini Project
Description
Build a small Compose screen that displays a list of users from LiveData. The project demonstrates how to observe LiveData safely, provide an initial value, and render the result without null-related crashes or delegation errors.
Goal
Create a composable that reads a user list from a ViewModel using observeAsState() and displays it as a simple text list.
Requirements
- Create a
ViewModelthat exposes aLiveData<List<User>>. - In a composable, observe that
LiveDatausingobserveAsState(). - Avoid nullable UI crashes by providing an initial value or handling null safely.
- Display each user's name in the UI.
- Use valid Compose state-reading syntax.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.
Android AlarmManager Example: Scheduling Tasks with AlarmManager
Learn how to use Android AlarmManager to schedule tasks, set alarms, and handle broadcasts with a simple beginner example.