Hello devs, Is it possible to create custom Room d...
# android
f
Hello devs, Is it possible to create custom Room database return types like rxjava3's flowables/observable/single, suspend and flowables, paging's data source etc.. I want room to return a type of my sealed class, just like you can do with retrofit's custom call adapters?
j
Im newer to Android dev but may be able to help you. I would return a flow from your Dao class. Collect the flow in your repository class and return the result of the flow as your sealed class.
g
I don’t think custom “call” adapters are supported. I think all supported types for code generation are hardcoded to annotation processor, but I’m not 100% sure, but it looks the case Just curious, what is your case for Sealed class for data wrapping?
f
Make db call State; Loading Success with result Call error with throwable Empty data if result is null That's my sealed class, so instead of a flow/flowable being mapped to a sealed class i wanted it directly coming out of room
g
what is Loading state in case of sql request? just initiali? You can encode it on level of flow, it probably would be just easier
honestly it looks that just use suspend function would be easier in this case, but maybe you have own specific case
f
That's what I'm currently doing But the return type is flow Then i have to collect that flow and map the flow into a sealed class result. Instead of doing that i wanted the converter/adapter to do this for me without any hussle like now
g
But the return type is flow
So you want a flow of those sealed classes?
Loading will never work properly for you, it also look a bit overkill. Error is quite rare thing, it’s essentially request error or fail some constraint checks (possible, but I wouldn’t handle it on level of every subscription, it’s probably better just fail in such case), so the only what you get is empty or not empty data, which look a bit as more hustle to handle, Kotlin nullable type is good enough for it. empty list is a bit different case, but still, even for empty list it doesn’t look necessary
f
Yup, I'm getting flow of those sealed classes anyway Consider this scenario: I make an insert to the database I need to show database operation indicator like loading I need to catch the throwable if it happens I need to know when the operation ends Currently I'm doing stateflow.value= Sealed.Loading viewModelScope.launch(ioDispatcher + SupervisorJob) { try { dao.insert(model) } catch (t: Throwable){ stateflow.value= Sealed.CallError(t) } finally { stateflow.value= Sealed.CallFinished } }
g
I need to catch the throwable if it happens
I really not sure that you should catch it though, probably crash is just fine for this case
So this case is actually pretty different, it’s not Flow as standard Room flow, because standard room flow will not work for insert, isn’t it?
I don’t think you need all this
viewModelScope.launch
if insert alredy suspend function
f
I just pointed out what I'm currently doing for insert/update/delete
g
so it becomes stateflow.value = Sealed.Loading dao.insert(model) stateflow.value= Sealed.CallFinished
or with try/catch, if you really want it
f
Should i use withcontext only?
g
no, why switch context if it already suspend function?
if function is not blocking, there is no reason to do this (if suspend function implemented correctly, which the case for Room)
f
Yes it's suspend update/delete/insert You must call suspend from a coroutine scope anyway
g
yeah, sure, but you also switch context
f
Not if u launch it with ioDispatcher
g
so I would just create a special coroutine builder, which automatically updates loading, finished and error cases
after all you still should launch this operation
f
Yeah how else u need to make the suspend call from within a scope...
g
so it can work like:
Copy code
CoroutineScope.launchWithState(state: StateFlow, block: suspend () -> Unit): Job {
  stateflow.value= Sealed.Loading
   try {
 block()
} catch (t: Throwable){
stateflow.value= Sealed.CallError(t)
} finally {
stateflow.value= Sealed.CallFinished
}
}
so your insert becomes:
Copy code
viewModelScope.launchWithState(stateflow) {
   dao.insert(model)
}
f
Exactly, you still need the scope Which is viewModelScope in my case
g
but you need scope in any case
even if you would be able to create this Flow with sealed class
Just to clarify, i’m not against scopes, I’m pointed out above just that you don’t need context switching like ioDispatcher + SupervisorJob
f
Supervisor job/scope is needed in case the job fails (which is rarely) and doesn't cancel the whole scope/other jobs
g
when you just do SupervisorJob() you coroutine violates structured concurrence and leaks (it’s not attached to any scope anymore)!
f
Copy code
ViewModel.viewModelSupervisorIOSupervised(crossinline function: suspend () -> Unit) {
    viewModelScope.launch(ioDispatcher) {
        supervisorScope {
            function()
        }
    }
}
what about this?
g
you don’t need supervisorScope, it’s useless, if you know that you alreaady run from viewModel
also usually you don’t need ioDispatcher, it needed only to wrap blocking calls, and suspend function never should be blocking