0

I have problem with collection songs from my database.

Song Dao:

   @Query("SELECT * FROM song_table")
    fun observeSongs() : Flow<List<DatabaseSongListItem>>

Repository:

fun observeSongs() = songDao.observeSongs()

ViewModel:

 private val _observeSongs = MutableStateFlow(emptyList<DatabaseSongListItem>())
    val observeSongs = _observeSongs.asStateFlow()
    viewModelScope.launch {
            songListRepository.getSongs() -> this line can get api songs + it add songs to database
            songListRepository.observeSongs().collect(){ songs ->
                _observeSongs.value = songs
            }
        }

MainActivity:

  lifecycleScope.launch {
            viewModel.observeSongs.collect {
                Log.d(ContentValues.TAG, "MAIN: $it")
            }

This code is working but im collecting my songs 2 times, 1 in viewModel and another one i activiy. I only want to collect it in my activity once. But when I use this line of code in viewModel I get this:

 _observeSongs.value = songListRepository.observeSongs()
Type mismatch.
Required:
List<DatabaseSongListItem>
Found:
Flow<List<DatabaseSongListItem>>

I want to show list of songs in recyclerView that is why I need List of songs.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Equlo
  • 115
  • 10
  • What is the actual problem that you're worried about? Converting a flow to a StateFlow necessitates collecting the upstream cold flow into the shared StateFlow (whether by your verbose way with a MutableStateFlow, or by simply using `stateIn`, which would do it under the hood). If you want to skip this step, then the Activity would be working directly with the cold flow, so you lose the benefit of not having to restart the flow (wasting time) when the activity has to be recreated. – Tenfour04 Oct 14 '22 at 13:42
  • I used _observeSongs.emitAll(songListRepository.observeSongs()), not sure is that optimal solution – Equlo Oct 14 '22 at 14:38

1 Answers1

1

I'm still unclear on exactly what your issue is. The clean way I would create a state flow and observe it is as follows, but I don't know if this achieves what you're trying to do exactly.

The SharingStarted setting will prevent unnecessary monitoring of the database when the Activity is off-screen, but it's optional.

ViewModel:

val observeSongs = songListRepository.observeSongs()
    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

init { // I'm assuming you were doing this in init, but maybe you have it in a function
    viewModelScope.launch { songListRepository.getSongs() }
}

Activity:

lifecycleScope.launch {
    viewModel.observeSongs.collect {
        myRecyclerViewAdapter.submitList(it)
    }
}

// or alternate syntax:
viewModel.observeSongs
    .onEach { myRecyclerViewAdapter.submitList(it) }
    .launchIn(lifecycleScope)
Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • Lets say I need to read documentation more. I still dont know proper difference between hot and cold flows, but I think this is it. Will try this later in my code and mark it as an answer. Thanks – Equlo Oct 14 '22 at 15:49
  • I am not sure about the internal implementation details but I think `stateIn` is a must as room exposes a cold flow so you can't really listen to the data changes forever unless you pipe it to a hot flow, right? – stdout Feb 04 '23 at 14:09
  • @stdout, if you leave it as a cold flow, you listen as long as your coroutine is alive. Supposing the coroutine runs in a lifecycleScope, then if it gets canceled when an Activity is recreated, you could just collect it again in another coroutine. But if you use a SharedFlow or StateFlow, that avoids wasting waiting for a new first value from the cold flow when the activity is recreated. So it’s not essential, but a good practice. – Tenfour04 Feb 04 '23 at 17:59
  • Maybe you misunderstand what a cold flow is. If you collected a cold flow in GlobalScope, you could listen forever. – Tenfour04 Feb 04 '23 at 18:01
  • you're right from the collector point of view indeed. but what if your cold flow is done emitting? then I think your producer block is finished and the connection/pipe between collector and producer is closed, right? so, if `Room` exposes a cold flow (without a while loop in it), the pipe in between gets closed once the producer emits it's last value. – stdout Feb 04 '23 at 20:07
  • 1
    That would be true if the cold flow emits a final value, but Room’s cold flows monitor the database indefinitely. And if they didn’t, `stateIn` wouldn’t help. When the source flow reaches its last value, the downstream StateFlow doesn’t restart it. It just sits in an unfinished state forever, never emitting another value again, except to new collectors, which will only be able to collect that final value. – Tenfour04 Feb 05 '23 at 07:56
  • that's actually what i wanted to find out. do you perhaps know how exactly this works? a flow with a while loop returned from `Room` perhaps? on the second point, i think the collected state flow keeps all the upstream cold flows (which are piped to it) active. if there's a cold upstream flow with a while loop or delays in it, then I believe they will keep producing/emitting data to the state flow. if cold flows already emitted their final value, then of course using state flow would not help. – stdout Feb 05 '23 at 11:57
  • I haven’t checked the source code, but I presume each LiveData or Flow returned by Room registers itself in a registry of listeners in the database class implementation. In the case of a Flow, it would register itself when collection begins and unregister when cancelled. The database would trigger all the registered listeners to rerun their queries each time a DAO modifies the database. Since Room supports LiveData and Rx Observable, there is probably a common base implementation for all three, or MutableLiveData is the basis. I think a callback basis is much more likely than a while loop. – Tenfour04 Feb 05 '23 at 15:39
  • More sensible than tying up a thread with a while loop. – Tenfour04 Feb 05 '23 at 15:47
  • i see…than i think it is rather a hot flow than a cold flow as it is tied to the db instance that’s in memory – stdout Feb 05 '23 at 21:11
  • No, that’s not what hot means. It may hold a reference to the database, but it is completely passive and not emitting anything when it is not being collected, so it is cold. – Tenfour04 Feb 05 '23 at 21:25
  • yes, that is clear. what i mean is, after the collection starts and the underlying flow emits it's last/final value. then in this case, there must be a way so the connection between the collector and underlying cold flow is not closed, right? so this is where cold and hot flow kind of converge to each other IMO. Of course, having a subscriber or not is still the determining factor to kick a cold flow off. – stdout Feb 06 '23 at 11:27
  • “Cold”/“hot” terminology have to do with whether the Flow reference is continually emitting regardless of whether it has collectors, and has nothing to do with how long it can stay alive after collection starts. There is no ambiguity at all that the flow is cold. You’re adding to the definition. – Tenfour04 Feb 06 '23 at 13:59