跪拜 Guibai
← Back to the summary

LiveData Was a Breakthrough, but It's Now the Wrong Abstraction for Most of Your App

The first time I encountered LiveData, I thought it was amazing.

Back then, Android development was full of Callbacks, lifecycle issues, and memory leak risks.

The emergence of LiveData gave Android development its first data observation solution integrated with the lifecycle.

At that time, I thought:

LiveData is really great, simple and easy to use.

But a few years later, I use LiveData less and less.

In new projects now, I basically no longer actively choose LiveData, and instead use Kotlin Flow more.

This is because as the complexity of Android applications increases, some of LiveData's own design limitations become more and more apparent.


1. Why did LiveData impress me when it first appeared?

What was Android asynchronous development like before LiveData?

Lots of Callbacks:

api.getUser(new Callback<User>() {

    @Override
    public void onSuccess(User user) {

        textView.setText(user.name);

    }

});

It looks simple, but hides many problems.


1. Lifecycle Issues

For example:

Open page

↓

Request network

↓

User goes back

↓

Activity destroyed

↓

API returns

↓

Update UI

↓

Crash

Developers needed to judge themselves:

if(!activity.isDestroyed()){

}

Lots of lifecycle judgment code.


2. Data and UI tightly coupled

Before:

Request success

↓

Callback

↓

Modify UI

Business code and UI updates mixed together.


LiveData appeared:

viewModel.user.observe(this){
    name.text = it.name
}

Suddenly it became very natural.

It solved:

At the time, this was a huge step forward.


2. LiveData's greatest value: giving Android lifecycle-aware data observation

LiveData's biggest design:

observe(owner){

}

It knows:

STARTED

RESUMED

DESTROYED

states.

For example:

Activity:

onStart

↓

Start receiving data


onDestroy

↓

Auto unbind

Developers no longer need to manually handle:

So LiveData's core contribution:

It solved the problem of "data observation" and "lifecycle management" in the Android world.


3. But LiveData's first limitation: it is not a true data stream

This is one of LiveData's biggest problems.

LiveData:

val user = MutableLiveData<User>()

It is more like:

An observable data container.

It holds:

Current value

Then notifies:

Observers

But many scenarios in modern applications are essentially:

Data streams.

For example, search:

User input:

a

↓

ab

↓

abc

We want:

No input for 300ms

↓

Request abc

Flow:

queryFlow
    .debounce(300)
    .flatMapLatest {

        search(it)

    }

Very natural.

Because Flow expresses:

Data generation

↓

Data transformation

↓

Data filtering

↓

Data consumption

While LiveData focuses more on:

What is the current value

4. LiveData lacks complete data stream processing capabilities

Modern applications heavily need:

Flow:

userFlow
    .filter {

    }
    .map {

    }
    .combine(otherFlow){

    }

These are all core capabilities of Flow.


Although LiveData has:

Transformations.map()
Transformations.switchMap()

Its capabilities are limited.

For something more complex:

For example:

User info

+

Configuration

+

Network status

+

Permission status

LiveData usually:

MediatorLiveData

Manually managing multiple data sources.

The code easily becomes more and more complex.


5. LiveData's second limitation: it is not suitable as a data type for the Data layer

This is a problem many projects encounter later on.

Early in many projects:

class UserRepository {
    fun getUser():LiveData<User>
}

At the time, it felt:

Very convenient.

But the problem arises:

LiveData belongs to:

androidx.lifecycle.LiveData

It is essentially an Android lifecycle component.


Why shouldn't the Data layer expose LiveData?

Because the data layer focuses on:

How data is generated

How data changes

Not:

Is the Activity displayed

Is the Fragment destroyed

But LiveData inherently focuses on:

LifecycleOwner

For example:

STARTED

RESUMED

DESTROYED

This means:

The data layer starts to know about:

The Android lifecycle model.


For example:

Database:

@Query(
"select * from user"
)
fun observeUser():LiveData<User>

Looks great.

But what if:

A background Service needs user data:

Or:

A Worker needs to listen for data:

What do they do?

Because they don't have:

LifecycleOwner

So various conversions start appearing:

asFlow()

In fact:

If the data source was Flow from the start:

These problems wouldn't exist.


6. LiveData restricts how data can be consumed

Suppose:

Repository:

fun message():LiveData<Message>

UI:

No problem:

observe()

But:

Background task:

Coroutine

Would prefer:

collect()

But LiveData:

Its design goal is:

Observer.

Thus:

The data producer determines the consumer pattern.


Whereas with Flow:

fun message():Flow<Message>

The consumer decides for themselves:

UI:

collectAsState()

Background:

collect()

Testing:

first()

Same data source:

Different consumption methods.


7. LiveData has always been awkward for event handling

Classic problem:

One-time events.

For example:

Login success:

loginSuccess.value = true

Page:

observe {

    Toast.makeText(
        "Login successful"
    )

}

Rotate the screen.

LiveData will re-emit:

true

Result:

The Toast appears again.

Thus emerged:

SingleLiveEvent

Or:

EventWrapper

But this also illustrates:

LiveData is more suitable for:

State.

Not suitable for:

Event streams.


Events:

Click once

Emit once

Consume once

State:

What is the current value

The semantics of the two are different.


8. LiveData's threading model is relatively simple, also limiting its capabilities

LiveData:

setValue()

Main thread.

Background:

postValue()

Looks simple.

But:

It hides some details.

For example:

postValue(1)

postValue(2)

postValue(3)

Ultimately:

You might only receive:

3

Reason:

LiveData's goal:

Preserve the latest state.

It is not a message queue.


But many scenarios require:

Event 1

Event 2

Event 3

All processed

For example:

LiveData is not suitable.


9. LiveData combined with Kotlin Coroutines is not as natural as Flow

Now the Android mainstream:

Coroutine

+

Flow

But LiveData:

Usually:

viewModelScope.launch {
      val result = repository.getUser()
       liveData.value = result
}

In the middle:

Coroutine

↓

LiveData

↓

Observer

An extra layer.


Flow:

viewModelScope.launch {

    repository.user()
        .collect {

        }

}

Naturally connected.


10. LiveData's biggest problem: great for simplicity, easy to lose control with complexity

LiveData:

Simple page:

val loading =
    MutableLiveData<Boolean>()

Very comfortable.

But complex page:

Multiple LiveData:

UserLiveData

+

ConfigLiveData

+

NetworkLiveData

+

PermissionLiveData

Finally:

MediatorLiveData

Transformations

Event wrappers

Various utility classes

Increasingly like:

Implementing your own data stream framework.


11. What is the essential difference between LiveData and Flow?

LiveData:

A lifecycle-aware data container.

Flow:

A data stream that can be composed, transformed, and cancelled.


LiveData mindset:

Data changed

Notify me

Flow mindset:

Data itself is a stream

I want to process this stream

Summary

LiveData is not wrong.

It was a very important part of the Android architecture evolution.

It solved:

Lifecycle-safe data observation.

But it also has clear limitations:

  1. It is not a complete data stream model

  2. Limited data combination capabilities

  3. Not suitable as a data type for the Data layer

  4. Integration with coroutines is not natural enough

  5. Inherent flaws in event handling

So:

LiveData is suitable for the UI layer, but should not become the data transmission model for the entire application.