Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Emit coroutine Flow from Room while backfilling via network request

I have my architecture like so:

  1. Dao methods returning Flow<T>:

    @Query("SELECT * FROM table WHERE id = :id")
    fun itemById(id: Int): Flow<Item>
    
  2. Repository layer returning items from DB but also backfilling from network:

    (* Need help here -- this is not working as intended **)

    fun items(): Flow<Item> = flow {
        // Immediately emit values from DB
        emitAll(itemDao.itemById(1))
    
        // Backfill DB via network request without blocking coroutine
        itemApi.makeRequest()
            .also { insert(it) }
    }
    
  3. ViewModel layer taking the flow, applying any transformations, and converting it into a LiveData using .asLiveData():

    fun observeItem(): LiveData<Item> = itemRepository.getItemFlow()
        .map { // apply transformation to view model }
        .asLiveData()
    
  4. Fragment observing LiveData emissions and updating UI:

    viewModel.item().observeNotNull(viewLifecycleOwner) {
        renderUI(it)
    }
    

The issue I'm having is at step 2. I can't seem to figure out a way to structure the logic so that I can emit the items from Flow immediately, but also perform the network fetch without waiting.

Since the fetch from network logic is in the same suspend function it'll wait for the network request to finish before emitting the results downstream. But I just want to fire that request independently since I'm not interested in waiting for a result (when it comes back, it'll update Room and I'll get the results naturally).

Any thoughts?

EDIT

Marko's solution works well for me, but I did attempt a similar approach like so:

suspend fun items(): Flow<List<Cryptocurrency>> = coroutineScope {
    launch {
        itemApi.makeRequest().also { insert(it) }
    }

    itemDao.itemById(1)
}
like image 409
0xMatthewGroves Avatar asked Oct 27 '25 08:10

0xMatthewGroves


1 Answers

It sounds like you're describing a background task that you want to launch. For that you need access to your coroutine scope, so items() should be an extension function on CoroutineScope:

fun CoroutineScope.items(): Flow<Item> {
    launch {
        itemApi.makeRequest().also { insert(it) }
    }
    return flow {
        emitAll(itemDao.itemById(1))
    }
}

On the other hand, if you'd like to start a remote fetch whose result will also become a part of the response, you can do it as follows:

fun items(): Flow<Item> = flow {
    coroutineScope {
        val lateItem = async { itemApi.makeRequest().also { insert(it) } }
        emitAll(itemDao.itemById(1))
        emit(lateItem.await())
    }
}
like image 146
Marko Topolnik Avatar answered Oct 29 '25 09:10

Marko Topolnik



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!