Issue
In my scenario there is a one-to-one relationship between transactions and categories.
In order to query the list of transactions and corresponding categories, documentation tells me i must first model the one-to-one relationship between the two entities (https://developer.android.com/training/data-storage/room/relationships) by creating a TransactionAndCategory
class
I really don't want to do this, it is not clean and it really is bad coding imo. Therefore i tried associating these objects by myself but i haven't found a way to do it. Here's what i tried to do:
class TransactionRepositoryImpl(private val transactionDao: TransactionDao, private val categoryDao: CategoryDao) : TransactionRepository {
override fun getAll(): Flow<List<Transaction>> {
return transactionDao
.getAll()
.mapIterable{
Transaction(it, categoryDao.get(it.categoryId))
}
}
}
This results in an error because categoryDao.get(it.categoryId)
returns a flow itself:
@Dao
interface CategoryDao {
@Query("SELECT * FROM categories WHERE uid = :id")
fun get(id: Int): Flow<DatabaseCategory>
}
The mapIterable function is just an extension to 'unwrap' the list:
inline fun <T, R> Flow<Iterable<T>>.mapIterable(crossinline transform: (T) -> R): Flow<List<R>> =
map { it.map(transform) }
Transactions itself are retrieved like this from the TransactionDao
:
fun getAll(): Flow<List<DatabaseTransaction>>
The constructor of my Transaction domain model takes two database models as arguments:
constructor(databaseTransaction: DatabaseTransaction, databaseCategory: DatabaseCategory)
I'm hoping someone with more Kotlin Flow experience than me has encountered the same issue and can provide me some ideas/insights/solutions on how to link these objects in a clean way without creating a TransactionAndCategory class.
EDIT: I tried suspending the categoryDao.get method:
@Query("SELECT * FROM categories WHERE uid = :id")
suspend fun get(id: Int): DatabaseCategory
override suspend fun getAll(): Flow<List<Transaction>> {
return transactionDao
.getAll()
.mapIterable{
Transaction(it, categoryDao.get(it.categoryId))
}
}
Unfortunatly it's not possible to call categoryDao.get
inside mapIterable
, so i'm still stuck on this
compilation error: "suspension functions can only be called only within coroutine body"
Solution
As said in the comment, a room database can return an instance of your object, when you change Dao#get
to a suspend fun Dao#get
.
In this example, CategoryDao#get
should not return a Flow, but the object itself. Since we can't / shouldn't access the db on the main-thread, we use coroutines.
Changing CategoryDao#get
to suspend fun get(): DatabaseCategory
and furthermore changing TransactionRepositoryImpl#getall
to suspend fun getAll
should solve your problems :)
Edit
override suspend fun getAll(): Flow<List<Transaction>> {
return transactionDao
.getAll()
.map { list ->
list.map {
Transaction(it, categoryDao.get(it.categoryId))
}
}
}
Or if you want to use your mapIterable function change it to the following
inline fun <T, R> Flow<Iterable<T>>.mapIterable(crossinline transform: suspend (T) -> R): Flow<List<R>> =
map { list ->
list.map { item ->
transform(item)
}
}
override suspend fun getAll(): Flow<List<Transaction>> {
return transactionDao
.getAll()
.mapIterable {
Transaction(it, categoryDao.get(it.categoryId))
}
}
Answered By - Andrew
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.