I want to retrieve single object from Room database, so i have this method in Dao
// in Dao
@Query("SELECT * FROM table_foo ORDER BY RANDOM()")
fun getSingleFoo(): Flow<FooEntity>
That object then will be mapped into others model, let say PlainFoo
.
// in Repository
fun getRandomFoo(): Flow<PlainFoo> = dao.getSingleFoo()
.map(FooEntity::asExternalModel)
But in the first launch of this app, the table is empty. It makes the dao function return null and trigger NPE when being mapped. I try to wrap it inside a sealed interface like this.
// Result.kt as wrapper
sealed interface Result<out T> {
data class Success<T>(val data: T) : Result<T>
data class Error(val exception: Throwable? = null) : Result<Nothing>
}
fun <T> Flow<T>.asResult(): Flow<Result<T>> = this
.map<T, Result<T>> {
Result.Success(it)
}
.catch {
emit(Result.Error(it))
}
And then i call this method in the presentation layer like this.
// in ViewModel
val randomFoo = fooRepository.getRandomFoo().asResult()
// in activity, log only for checking
lifecycleScope.launch {
viewModel.randomFoo.collect {
Timber.tag("RandomFooFlow").d("$it")
}
}
It catches the error, which look like this.
Error(exception=java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter <this>)
But when new data is inserted, it does not get updated unless i reopen the app (which means new Flow is being collected, not the old one). So it seems that the flow is cancelled.
Is there any way to handle this without making my Dao return a nullable object?
Note: if the data is already populated when opening the app, the flow is able to keep consuming new value).
CodePudding user response:
Could you please call repository getRandomFoo() method from inside coroutine in view model ? And also you need to call response with data observe like LiveData or StateFlow. By the way, you can wrap your result with wrap inside repository. In code example, I do not care about it because your error is not related with mapping.
View Model
private val _stateFlow = MutableStateFlow()
val stateFlow:StateFlow
fun getRandom(){
fooRepository.getRandomFoo().onEach{
if(it is Result.Success){
stateFlow.value = it
}
}.launchIn(viewModelScope)
}
Fragment or activity
viewLifecycleOwner.lifecycle.repeatOnLifecycle{
stateFlow.collect{
// Listen data for your UI
}
}
CodePudding user response:
Instead of dealing with exceptions, I would suggest to return nullable types from your Dao. You can then also update your mapper function to handle the type nullability. You won't need to wrap it into any Result
class, just a simple null check on the UI end would suffice.
// Dao
@Query("SELECT * FROM table_foo ORDER BY RANDOM()")
fun getSingleFoo(): Flow<FooEntity?>
// Repo
fun getRandomFoo(): Flow<PlainFoo?> = dao.getSingleFoo().map { it?.asExternalModel() }