3 reasons why I'm not migrating LiveData to StateFlow

ยท

4 min read

I have a confession to make, after reading through StateFlow and SharedFlow documentation, watching Android Developers's video, and reviewing nowinandroid source codes, I'm not convinced to migrate LiveData to StateFlow or SharedFlow. At least for now. Here's why:

1. Most of the time, I only do one-shot operation

According to Kotlin documentation flow is "An asynchronous data stream that sequentially emits values and completes normally or with an exception."

There are valid use-cases for using flow, like fetching the latest data from server periodically or receiving live-update of query result when using Room.

But, most of the time, I only need to do a one-shot task. Not more, not less. Such as, making a GET request to a server and retrieving a value from a database. In those situations, the nature of my task is not stream. Even though I can convert it to flow:

flow {
    val weather = weatherApi.getWeather(coordinate)
    emit(weather)
}

This works, yet in my opinion, I added an extra line of codes for no specific reason.

We can also create a StateFlow or SharedFlow without flow.

private val _myState = MutableStateFlow(
    MyState(
        title = R.string.title,
        index = 0,
    )
)
val myState: StateFlow<MyState> = _myState.asStateFlow()

But again, if it's not emitting multiple values sequentially, why should I add more code?

This brings us to my second reason.

2. LiveData is lifecycle-aware

StateFlow and SharedFlow are part of Kotlin library. It's designed to be platform agnostic (go KMM! ๐Ÿš€), so it doesn't know about Android framework and its lifecycle. If you want to integrate it Android, you need to ensure it follows Android lifecycle.

// Activity

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect { uiState ->
            when (uiState) {
                is UiState.Success -> showSuccess(uiState.data)
                is UiState.Error -> showError(uiState.exception)
            }
        }
    }
}

If only there is a Jetpack library for that.. ๐Ÿค”

That's what LiveData is made for! Taken from Android documentation, "LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services.".

The same functionality can be achieved with this code.

// Activity

viewModel.uiState.observe(this) { uiState ->
    is UiState.Success -> showSuccess(uiState.data)
    is UiState.Error -> showError(uiState.exception)
}

By using LiveData, we don't need to manually handle the lifecycle, worrying memory leaks, and be sure that the data is always up-to-date.

One caveat, I realize in Compose the difference isn't significant (Kudos to Jetpack team ๐Ÿ‘)

@Composable
fun HomeRoute(
    modifier: Modifier = Modifier,
    viewModel: MyViewModel = getViewModel<MyViewModel>()
) {
    val uiStateLiveData by viewModel.uiState.observeAsState(initial = UiState.Loading)
    val uiStateStateFlow by viewModel.uiState.collectAsStateWithLifecycle()
}

The stable version of Jetpack Compose is just released last year. While I do like writing UI using Compose, I think most companies still need more time to adopt Compose.

3. I don't need StateFlow or SharedFlow features

StateFlow and SharedFlow have many features for configuring the emitted elements. SharedFlow has a reply functionality that can resend previously-emitted values for new subscribers. StateFlow has a time-out mechanism before stopping upstream flow using WhileSubscribed class.

val uiState: StateFlow<UiState> = flow {
    emit(repository.getItem())
}.stateIn(
    scope = viewModelScope, 
    initialValue = UiState.Loading,
    started = WhileSubscribed(5_000),
)

flow also allows you to combine multiple flows into a StateFlow.

val uiState: StateFlow<UiState> = combine(
    userRepository.getUser(),
    weatherRepository.getWeather(),
    statusRepository.getStatusStream(),
) { user, weather, status stream -> {
    // ...
}

Whereas, in my projects, the cool features that SharedFlow and StateFlow provides is a nice-to-have, not a must-have. The complex operation is handled on different classes, like on Repository (data layer) or UseCases (domain layer).

For merging multiple data-sources, like network and database, I can use Repository backed with NetworkDataSources and DbDataSources. For data manipulation, Kotlin's collection has many useful functions (map, reduce, groupBy, count, etc.). If I want to ensure a data can only be emitted once, a simple modification to LiveData is enough (SingleLiveData

Summary

So, when should you use StateFlow and SharedFlow? If you need them! Use the right tool for the right job.

If you need to store or buffer flow elements, if you need to support KMM in your project, or if you need first-class support for stream operation, then please use StateFlow and SharedFlow ๐Ÿ™.

If you think LiveData is enough for you, don't migrate to StateFlow just yet ๐Ÿ˜

#4articles4weeks #week1 #android #kotlin #jetpack #coroutine

ย