Exploring use of StateFlow in a Kotlin Multiplatform project

Share on:

Kotlinx Coroutines recently introduced the StateFlow API, described as “A Flow that represents a read-only state with a single updatable data value that emits updates to the value to its collectors”. This is generally seen as a replacement for LiveData on Android but also, importantly, something that can be used in shared Kotlin Multiplatform code.



In a previous post I started exploring use of Kodein-DB NoSQL database. In this initial implementation we fetched data from remote endpoint at startup and stored that data in the DB (along with api in respository to read that data). What we’d generaly want in this scenario is a more reactive approach where UI layer is notified of any changes to DB and makes updates accordingly. As such I’ve made some changes to BikeShare repo that now uses Kodein-DB api to register for updates to DB along with use of StateFlow in the repository class. In our ViewModel then we can now take advantage of Flow operators available to map that data to version required by Jetpack Compose UI code.

CityBikesRepository (part of shared Kotlin Multiplatform code) now includes following. We have a StateFlow value representing the list of bike share networks (grouped by country)….which is updated whenever that list chages in the DB.

private val _groupedNetworkList = MutableStateFlow<Map<String,List<Network>>>(emptyMap())
val groupedNetworkList: StateFlow<Map<String,List<Network>>> = _groupedNetworkList

...

db.on<NetworkList>().register {
    didPut { networkListData ->
        _groupedNetworkList.value = networkListData.networks.groupBy { it.location.country }
    }
}


BikeShareViewModel in turn was updated to include following. The data from the repository just includes country code so we’re using Flow’s map operator to convert to version that also includes the country name. This is relatively basic example but one of the strength’s of using StateFlow here (code had been using LiveData in a previous version) is the rich set of operators now available.

val groupedNetworks = cityBikesRepository.groupedNetworkList.map {
    it.mapKeys {
        val countryCode = it.key.toLowerCase()
        val locale = Locale("", countryCode)
        val countryName = locale.displayCountry
        Country(countryCode, countryName)
    }
}

Finally the Jetpack Compose code can use collectAsState() extension function to convert that to a State value.

val groupedNetworkListState = bikeShareViewModel.groupedNetworks.collectAsState(initial = emptyMap())

As with any new capability I think patterns will emerge over time as to how best to utilise StateFlow (and also new SharedFlow). What we’ve shown here is relatively basic example where StateFlow seemed to fit well. Depending on nature of data and how it’s updated it may be more appropriate to use Flow itself (still with use of StateFlow in ViewModel perhaps utilising new stateIn function).


And this btw is what Jetpack Compose UI that uses the data looks like

Android Bike Share Country List Screen


Featured in Kotlin Weekly Issue #222