Search code examples
kotlinandroid-mvvmkotlin-stateflow

Best way to use ArrayList in in Kotlin/MVVM. StateFlow vs Flow vs suspend


I am trying to upgrade my java code to kotlin but I don't know how to aproach this transition. It seems that I have to perfectly understand corutines which is a pain because it seems too much all at once just to make a decision how to start.

To summarize my app have two ArrayList

/**
This array must update exactly every minute from db(room) or retrofit
The array itself usually does not change but the size of it does (objects inside can change). 
*/
val schedulesArray: ArrayList

/**
Objects in this array should updates whenever the db(room) changes
The array size can change (objects can be added or removed) 
*/
val channelsArray: ArrayList

My question is strictly about what should I use to efficiently implement corutines: Flow or suspend or StateFlow... Should I pass the entire array to this Flow, StateFlow ... and modify it later when I need it or should I create thousand of these for each object inside array. How can I modify the array inside eg. StateFlow later when needed? Where should I iterate the array? ModelView or Repository?


Solution

  • You need all three to efficiently handle data in your app.

    The usual way to go is to use immutable objects in a Flow that contain your data. Your business logic then only transforms the content of the flow by replacing one immutable object by another. Only convert the Flow into a StateFlow at the latest possible moment, just right before it is handed to the UI to be collected.

    Room, for example, already supports flows out of the box. You would define the return value of your query methods to return a Flow of List:

    @Query("SELECT * FROM data")
    fun allData(): Flow<List<Data>>
    

    The List here implies a list that cannot be changed. In geneal you should only use List or MutableList and don't use ArrayList directly. You create new lists by either calling listOf() (or emptyList()) and mutableListOf() respectively.

    When you retrieve the data from your Room Dao you can then transform the data by using any of the various flow transformation functions like mapLatest, flatMapLatest, combine and so on.

    The flow will only be collected in the UI. For that it is usually the easiest to convert the Flow into a StateFlow using stateIn first. When you use a view model that's usually where that is done. A StateFlow only remembers the last value, but it doesn't keep a history of older values. It always has a single value, much like a variable, but unlike a variable the value automatically updates when the underlying flows emit. That makes it easy for your UI to subscribe and unsubscibe the StateFlow while always getting the current state your app is in (hence the name).

    If you have APIs that don't support flows you can convert any callback based api into a flow by using a callbackFlow.

    Regarding suspend functions, you don't want to use them to pass your data up the layers, that is what your Flows are for. A function should usually either return a Flow or be a suspend function. You usually only need suspend functions to pass events down through your layers. That means, for example, that an insert function in your Room Dao would look like this:

    @Insert
    suspend fun insertData(data: Data)
    

    Calling this requires you to launch a coroutine or already be in another suspend function. The coroutines should be usually created high up in your layered architecture so that they can be easily cancelled (when the user decides to abort an operation or leaves a screen and so on). You need a CoroutineScope to launch new coroutines. Where you can get a coroutine scope or if you need to create one yourself depends on the Frameworks you use. Android's ViewModel for example provides a ready-to-use viewModelScope, so new coroutines are usually launched there.