0

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"

General Grievance
  • 4,555
  • 31
  • 31
  • 45
DennisVA
  • 2,068
  • 1
  • 25
  • 35
  • `CategoryDao#get` can also return an instance of `DatabaseCategory`, when you change the function from `fun get(): Flow` to `suspend fun get(): DatabaseCategory`. With this, you should be able to manually map the one-to-one relationship. For this, you also have to change `TransactionRepositoryImpl#getall` to a `suspend fun getAll` – Andrew Aug 29 '21 at 12:09

1 Answers1

1

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))
        }
}
Andrew
  • 4,264
  • 1
  • 21
  • 65
  • the category.Get(it.categoryId) still gives me an error after these changes: "suspension functions can only be called only within coroutine body", even though the getAll() method is suspended now. This is probably because it is in the MapIterable right? – DennisVA Aug 29 '21 at 12:29
  • @DennisVA Yes, I did forget that. Just add "suspend" to your mapIterable function – Andrew Aug 29 '21 at 18:37
  • 1
    a big thanks to you for resolving this question, i wish i had your level of insight on this stuff. Cheers! – DennisVA Aug 29 '21 at 19:52