Question
Updating MutableLiveData from Coroutines in Kotlin: setValue vs postValue
Question
I am trying to update a LiveData value from inside a coroutine:
object AddressList : MutableLiveData<List<Address>>()
fun getAddressesLiveData(): LiveData<List<Address>> {
AddressList.value = listOf()
GlobalScope.launch {
AddressList.value = getAddressList()
}
return AddressList
}
However, I get this error:
IllegalStateException: Cannot invoke setValue on a background thread
How can MutableLiveData be updated correctly when using Kotlin coroutines?
Short Answer
By the end of this page, you will understand why MutableLiveData.value can only be set on the main thread, how postValue() differs from setValue(), and how to use coroutines safely with LiveData in Android. You will also learn better coroutine patterns than GlobalScope.launch for real projects.
Concept
MutableLiveData is designed to hold UI-related state in Android. Because UI state is closely tied to the main thread, LiveData enforces a simple rule:
- Use
value/setValue()on the main thread - Use
postValue()from a background thread
In your example, GlobalScope.launch does not guarantee execution on the Android main thread in a way that makes your value assignment safe for all cases. When the coroutine runs on a background thread and you call:
AddressList.value = getAddressList()
that internally calls setValue(), which throws this exception:
IllegalStateException: Cannot invoke setValue on a background thread
This matters because Android UI code must be thread-safe. If multiple threads could directly change UI-observed state without coordination, apps would become unpredictable and crash more often.
The usual solutions are:
- call
postValue()from a background thread - switch to
Dispatchers.Mainbefore assigning tovalue
Mental Model
Think of MutableLiveData like a notice board in a school office.
setValue()is like walking into the office and pinning a note directly onto the board. You can only do that when the office is open and staff are present — that is the main thread.postValue()is like dropping the note into a mailbox. Staff will later take it out and pin it to the board safely — that is a background thread posting work to the main thread.
So if your coroutine is working in the background, you should not walk straight to the board. You should either use the mailbox (postValue) or return to the office (Dispatchers.Main) first.
Syntax and Examples
The two most important ways to update MutableLiveData are:
liveData.value = newValue // main thread only
liveData.postValue(newValue) // safe from background thread
Example 1: Using postValue() from a coroutine
val addressList = MutableLiveData<List<Address>>()
fun getAddressesLiveData(): LiveData<List<Address>> {
addressList.value = emptyList()
GlobalScope.launch(Dispatchers.IO) {
val result = getAddressList()
addressList.postValue(result)
}
return addressList
}
Here:
Dispatchers.IOis used for background workgetAddressList()runs off the main threadpostValue(result)safely schedules the update
Example 2: Switching back to the main thread
val addressList = MutableLiveData<List<Address>>()
fun getAddressesLiveData(): LiveData<List<Address>> {
addressList.value = emptyList()
GlobalScope.launch(Dispatchers.IO) {
result = getAddressList()
withContext(Dispatchers.Main) {
addressList.value = result
}
}
addressList
}
Step by Step Execution
Consider this example:
val users = MutableLiveData<List<String>>()
fun loadUsers() {
users.value = emptyList()
GlobalScope.launch(Dispatchers.IO) {
val result = listOf("Ana", "Ben", "Chris")
users.postValue(result)
}
}
Step by step:
-
users.value = emptyList()runs on the current thread.- If called from the UI layer, this is usually the main thread.
usersnow contains an empty list.
-
GlobalScope.launch(Dispatchers.IO)starts a coroutine on an IO/background thread.- This is suitable for network or database work.
-
val result = listOf("Ana", "Ben", "Chris")- The background work completes and produces data.
-
users.postValue(result)is called.- Because this is a background thread,
postValue()is the safe choice. - Android schedules the LiveData update to happen on the main thread.
- Because this is a background thread,
Real World Use Cases
This pattern appears often in Android apps:
- API calls: fetch data in
Dispatchers.IO, then updateLiveData - Database queries: read from Room or another database in the background and publish results
- File loading: parse files or JSON off the main thread, then notify the UI
- Form validation: compute validation results, then expose them to the screen
- Progress updates: background work posts loading, success, or error state
Example with loading state:
data class UiState(
val loading: Boolean,
val addresses: List<Address> = emptyList(),
val error: String? = null
)
A ViewModel may update this state before, during, and after a request. The key idea stays the same: background work should not directly call setValue() unless you first switch back to the main thread.
Real Codebase Usage
In real projects, developers rarely expose a mutable LiveData directly. A common pattern is:
private val _data = MutableLiveData<List<Address>>()
val data: LiveData<List<Address>> = _data
This prevents outside classes from modifying the value.
Common patterns
1. Early loading state
_addresses.value = emptyList()
or a dedicated loading flag:
_loading.value = true
2. Background fetch + main-thread update
viewModelScope.launch {
try {
val result = withContext(Dispatchers.IO) {
repository.getAddressList()
}
_addresses.value = result
} catch (e: Exception) {
_error.value = e.message
}
}
3. Error handling
Coroutines often wrap repository calls in try/catch so the UI can observe failures safely.
4. Repository separation
A repository usually fetches data, while the ViewModel updates LiveData:
Common Mistakes
1. Using value from a background thread
Broken code:
GlobalScope.launch(Dispatchers.IO) {
liveData.value = fetchData()
}
Why it fails:
valuecallssetValue()setValue()must run on the main thread
Fix:
GlobalScope.launch(Dispatchers.IO) {
liveData.postValue(fetchData())
}
or:
GlobalScope.launch(Dispatchers.IO) {
val result = fetchData()
withContext(Dispatchers.Main) {
liveData.value = result
}
}
2. Using GlobalScope for UI work
Broken idea:
GlobalScope.launch {
// UI-related async work
}
Why it is risky:
- it is not tied to Activity or ViewModel lifecycle
- it may leak work or update dead screens
Better:
viewModelScope.launch {
}
Comparisons
| Concept | When to use | Thread rule | Notes |
|---|---|---|---|
liveData.value = x | When already on the main thread | Main thread only | Immediate LiveData update |
liveData.setValue(x) | Same as value = x | Main thread only | value is property syntax for this |
liveData.postValue(x) | When on a background thread | Any thread | Posts update to main thread later |
withContext(Dispatchers.Main) { liveData.value = x } | When you want to explicitly switch to main | Safe after switching |
Cheat Sheet
liveData.value = data // main thread only
liveData.postValue(data) // background thread safe
Rules
valueandsetValue()must run on the main threadpostValue()can be called from a background thread- Use
Dispatchers.IOfor blocking work - Use
Dispatchers.Mainfor UI state updates - Prefer
viewModelScopeoverGlobalScopein Android UI code
Common coroutine pattern
viewModelScope.launch {
val result = withContext(Dispatchers.IO) {
repository.loadData()
}
_liveData.value = result
}
Background-thread alternative
viewModelScope.launch(Dispatchers.IO) {
val result = repository.loadData()
_liveData.postValue(result)
}
Good encapsulation
private _items = MutableLiveData<List<Item>>()
items: LiveData<List<Item>> = _items
FAQ
Why does MutableLiveData.value crash inside a coroutine?
A coroutine can run on different threads. If it is running on a background thread, assigning to value calls setValue(), which is only allowed on the main thread.
Should I use postValue() or value with coroutines?
Use postValue() if you are on a background thread. Use value if you are already on the main thread or after switching with withContext(Dispatchers.Main).
Is GlobalScope.launch a good idea for LiveData updates?
Usually no. In Android UI code, viewModelScope is preferred because it is lifecycle-aware and cancels work automatically when the ViewModel is destroyed.
Can I call postValue() on the main thread?
Yes, but it is usually unnecessary. If you are already on the main thread, value is often clearer.
What is the difference between value and setValue()?
They are effectively the same for . is Kotlin property syntax that calls .
Mini Project
Description
Build a small ViewModel that loads a list of addresses in the background and exposes them to the UI through LiveData. This demonstrates the correct way to combine coroutines, background work, and main-thread-safe UI state updates.
Goal
Create a lifecycle-aware ViewModel that fetches address data asynchronously and updates LiveData without triggering thread errors.
Requirements
- Create a
ViewModelwith a privateMutableLiveData<List<Address>>and a publicLiveData<List<Address>>. - Add a function that loads addresses asynchronously.
- Perform the data fetch on
Dispatchers.IO. - Update the
LiveDatasafely when the result is ready. - Handle errors by exposing an error message through another
LiveData.
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.