Search code examples
androidkotlingenericsmvvmandroid-room

Generic wrappers with room database


I'd like to implement generic wrapper of my room database. The one I've implement on my own, doesn't seem to use it. Can you help me with it? Firstly, here's some code:

Wrapper

sealed class MyState{
data class Success<out T>(val data: T) : DatabaseState()
data class Error<out T>(val error: T) : DatabaseState()
}

Repository

    val myCurr: Flow<MyModelClass> =
    mydao.getCurr().shareIn(
        scope, replay = 1, started = SharingStarted.WhileSubscribed()
    )

ViewModel

private val _myState: MutableSharedFlow<MyState<myModel>> = MutableSharedFlow(replay = 1)
val stateGetter: SharedFlow<MyState<myModel>> get() = _myState
    

    fun myFun() {
    viewModelScope.launch(Dispatchers.IO ) {
        myRepository.myCurr
            .catch { _myState.emit(MyState.Error(it.cause))
            .collect { myModel->
                _myState.emit(DatabaseState.Success(myModel.curr))
            }
    }
}

It is still kinda new stuff for me, so the code probably won't be best. I just wondering, if my Repository class is correctly. I've read somewhere (probably in documentation) that I shouldn't use functions for getting values from db, instead, I should use val. Is there any way to use shareIn, and store it in generic wrapper? Is it good approach?


Solution

  • val properties are appropriate for flows that don't need an input. A function would be necessary when you need to retrieve a flow that is based on a specific input, as @LuisPascual's answer demonstrates. It would not make sense to create a SharedFlow and return it in a function, though, because then each function caller would be getting a different SharedFlow instance (nothing would be shared).

    So your repository code is perfect. But your ViewModel code has a couple of issues.

    1. You have a function that must be called to make your SharedFlow work. This is pointless convolution. Why not just do this work in an init block so it will happen automatically without you having to remember to call myFun somewhere, but being super careful not to call myFun more than once? If an outside class, such as a Activity calls it when it is created, it will be very difficult to prevent it from being called more than once.

    2. Even simpler, just use shareIn. Then you don't even need to do the above in an init block and you don't need a separate backing MutableSharedFlow property.

    3. You don't need to specify a dispatcher to call a Flow from room! Really the only time you should ever be using the flowOn operator is in a chain of calls after a flow { } builder (or similar) call or a map operator that does blocking work, because any Flow you get from another class should already be internally delegating itself appropriately (following the principles of separation of concerns and encapsulation). A Room DAO is never going to return a fragile Flow that requires some specific Dispatcher to avoid blocking a thread.

    So all your ViewModel code above should be replaced with:

    val stateGetter: SharedFlow<MyState<myModel>> =
        myRepository.myCurr
            .map { DatabaseState.Success(myModel.curr) }
            .catch { _myState.emit(MyState.Error(it.cause))
            .shareIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), replay = 1)
    

    Also, I'm not sure if you intended to make your flow a shared flow in both the repository and the ViewModel.