Blog Infos
Author
Published
Topics
,
Published

As Kotlin’s Flow library has stabilized and matured, Android developers have been encouraged to migrate from LiveData to StateFlow for managing observable view state. StateFlow is an improvement over LiveData, but for all its benefits StateFlow noticeably lacks one important mechanism that LiveData afforded us: Transformations. For a solid explanation of how transformations work with LiveData, check out this article by Peter Törnhult. Here’s the gist:

val movie: LiveData<Movie> = ...
val title: LiveData<String> = Transformations.map(movie) { it.title }
val cast: LiveData<List<Actor>> = Transformations.switchMap(movie) { getCast(it) }
val movie: StateFlow<Movie> = ...
val title: StateFlow<String> = movie.mapState { it.title }
val cast: StateFlow<List<Actor>> = movie.mapState { getCast(it) }
Step 1. Transform data with mapLatest
fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R>
fun <T, R> Flow<T>.map(transform: suspend (T) -> R): Flow<R>

These are called as follows:

val titles: List<String> = movies.map { it.title } 
val titleFlow: Flow<String> = movieFlow.map { it.title }

Since a StateFlow is a type of Flow, we have all of the Flow operators available to us. In this case we will use a variation on map called mapLatest. mapLatest works like map except that it cancels any pending transform operations. So if the Flow emits 100 values, map will map every value, while mapLatest will only map the terminal value. Since our StateFlow is only interested in representing the latest value anyway, this gives us a slight performance benefit at no cost. To return to our example above, we can use mapLatest to transform a Flow of Movie objects to a Flow of String titles.

val movie: StateFlow<Movie> = …
val titles: Flow<String> = movie.mapLatest { it.title }
view raw map-to-flow.kt hosted with ❤ by GitHub
Step 2. Obtain a hot flow
val titles: Flow<String> = …
val latestRelease: StateFlow<String> = movies.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = ""
)
val movie: StateFlow<Movie> = …
val title: StateFlow<String> = movie.mapLatest {
it.title
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = ""
)
view raw state-in-map.kt hosted with ❤ by GitHub
Step 3. Simplify with extension functions
fun <T, K> StateFlow<T>.mapState(
scope: CoroutineScope,
transform: (data: T) -> K
): StateFlow<K> {
return mapLatest {
transform(it)
}
.stateIn(scope, SharingStarted.Eagerly, transform(value))
}
fun <T, K> StateFlow<T>.mapState(
scope: CoroutineScope,
initialValue: K,
transform: suspend (data: T) -> K
): StateFlow<K> {
return mapLatest {
transform(it)
}
.stateIn(scope, SharingStarted.Eagerly, initialValue)
}
view raw state-in-map.kt hosted with ❤ by GitHub

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Building StateFlows in Android with Jetpack Compose

Compose is not just a UI toolkit. It is a general purpose tool for working with a trees of nodes. In this talk, I will present a library called Molecule that uses Compose to create…
Watch Video

Building StateFlows in Android with Jetpack Compose

Mohit Sarveiya
Google Developers Expert

Building StateFlows in Android with Jetpack Compose

Mohit Sarveiya
Google Developers Ex ...

Building StateFlows in Android with Jetpack Compose

Mohit Sarveiya
Google Developers Expert

Jobs

val title = movie.mapState(viewModelScope) {
it.title
}
fun <T, K> StateFlow<T>.mapState(
transform: (data: T) -> K
): StateFlow<K> {
return mapLatest {
transform(it)
}
.stateIn(viewModelScope, SharingStarted.Eagerly, transform(value))
}
fun <T, K> StateFlow<T>.mapState(
initialValue: K,
transform: suspend (data: T) -> K
): StateFlow<K> {
return mapLatest {
transform(it)
}
.stateIn(viewModelScope, SharingStarted.Eagerly, initialValue)
}

This provides a much cleaner, more readable API surface, since we are able to hide the details of the CoroutineScope and initial value. Our usage now looks like this:

val movie: StateFlow<Movie> = ...
val title: StateFlow<String> = movie.mapState { it.title }
abstract class BaseViewModel: ViewModel() {
fun <T, K> StateFlow<T>.mapState(
transform: (data: T) -> K
): StateFlow<K> {
return mapState(
scope = viewModelScope,
transform = transform
)
}
fun <T, K> StateFlow<T>.mapState(
initialValue: K,
transform: suspend (data: T) -> K
): StateFlow<K> {
return mapState(
scope = viewModelScope,
initialValue = initialValue,
transform = transform
)
}
}

Voila! Clean StateFlow transformations. Thanks for reading! I hope you’ve found this article helpful. Feel free to drop some comments into the discussion, and hit the “Follow” button for more on best practices in Kotlin and Android development. Happy coding!

 

This article was originally published on proandroiddev.com on March 09, 2022

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE
Menu