Room database with one-to-one relation
Asked Answered
M

3

14

I have 2 Entities, Coin and CoinRevenue.

Basically, coin holds the price in USD for some other currency.

For example, Coin with symbol EUR with value of 1.0356

@Entity(tableName = "coin")
data class Coin(
        @field:PrimaryKey(autoGenerate = false)
        var id: String = "",
        var symbol: String = "",
        var pricInUsd: Float = 0f)

CoinRevenue is an Entity that I use to hold how much coins of that specific coins the User have. For example, CoinRevenue has relation to Coin Entity with EUR symbol and amount of 1000.

@Entity(tableName = "coinRevenue")
    data class CoinRevenueNew(
            @field:PrimaryKey(autoGenerate = true)
            var id: Int = 0,
            var coin: Coin? = null,
            var amount: Float = 0f)

Now I want to fetch CoinRevenue from the database and get the updated Coin from the database.

for example, i saved the Coin with (EUR,1.0253) and than Saved a CoinRevenue with that coin.

After that I updated the Coin with (EUR,2.522) I want that the Coin object inside CoinRevenue will be updated as well.

I understand that @Embedded just add the inner objet fields as colums to the same parent object. and when I use relation, I have to use a List or a Set. but I always have 1 Coin inside CoinRevenue.

My coinDAO:

@Query("select * from coin order by rank")
fun getAllCoins(): Flowable<List<CoinDB>>

@Query("select * from coin where rank = 1")
fun getFirstCoin(): Maybe<CoinDB>

@Query("select * from coin where favourite = 1 order by rank")
fun getAllFavouriteCoins(): Flowable<List<CoinDB>>

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertCoin(coinDB: CoinDB)

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertCoins(coinsList: List<CoinDB>)

// -----------------
// CoinRevenue
// -----------------

@Query("select * from coinRevenue order by rank")
fun getAllCoinsRevenue(): Flowable<List<CoinRevenue>>

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertCoinRevenue(coinRevenue: CoinRevenue)

@Delete()
fun deleteCoinRevenue(coinRevenue: CoinRevenue)

What is the best way to creat this?

Myriagram answered 3/1, 2018 at 14:18 Comment(6)
You can use a Foreign key android.jlelse.eu/…Sapid
@AhmedHegazy I saw that post, but how do i make a query, to get the coinRevenue with the coin inside?Myriagram
You can use @Relation annotation developer.android.com/reference/android/arch/persistence/room/…Sapid
@AhmedHegazy, @Relation seems to me not the proper solution to the one-to-one relation problem. The documentation says The type of the field annotated with Relation must be a List or Set. so it doesn't fit very well with a one-to-one relation. Looking at Yigit Boyar introduction to Room, it seems to me that the best solution would be to create a third POJO class.Hipster
@Hipster Yes, you're right :+1:Sapid
@Hipster can you give an example on how to implement that third POJO?Myriagram
M
16

So after a lot of tries, I've managed to get it working.

I Changed the CoinRevenue object to hold a foreign key to the Coin id

@Entity(tableName = "coinRevenue", foreignKeys = (arrayOf(ForeignKey(
        entity = CoinDB::class,
        onUpdate = ForeignKey.CASCADE,
        parentColumns = arrayOf("coinId"),
        childColumns = arrayOf("coinDbId"))))
)
data class CoinRevenue(
        @ColumnInfo(name = "mid")
        @PrimaryKey(autoGenerate = true)
        var id: Long = 0L,
        @ColumnInfo(name = "coinDbId")
        var coinDbId: String? = null,
        @ColumnInfo(name = "amount")
        var amount: Double = 0.toDouble()
)

I needed to create a POJO with both objects, like that:

class CoinRevenueWithCoin() : Parcelable {
@Embedded lateinit var coinDB: CoinDB
@Embedded lateinit var coinRevenue: CoinRevenue
}

and the query it like this:

@Query("select * from coinRevenue, coin where coinRevenue.coinDbId = coin.coinId order by coin.rank")
fun getAllCoinsRevenueWithCoin(): Flowable<List<CoinRevenueWithCoin>>

That's it.

In addition this query, as any other regular objects query, emit objects if there is any change in the 'coin' table or the 'coinRevenue' table

Myriagram answered 9/1, 2018 at 16:45 Comment(0)
C
6

Your solution has several major drawback. One of them is that the tables' columns has to have different names. Instead of using @embededed I suggest to apply @Relation.

@Entity(tableName = "coin")
data class Coin(
        @field:PrimaryKey(autoGenerate = false)
        var id: String = "",
        var symbol: String = "",
        var pricInUsd: Float = 0f)

@Entity(tableName = "coinRevenue", foreignKeys = (arrayOf(ForeignKey(
        entity = CoinDB::class,
        onUpdate = ForeignKey.CASCADE,
        parentColumns = arrayOf("coinId"),
        childColumns = arrayOf("coinDbId"))))
)
data class CoinRevenue(
        @ColumnInfo(name = "mid")
        @PrimaryKey(autoGenerate = true)
        var id: Long = 0L,
        @ColumnInfo(name = "coinDbId")
        var coinDbId: String? = null,
        @ColumnInfo(name = "amount")
        var amount: Double = 0.toDouble()
) 

I am not familiar with Kotlin so the solution is in Java

class CoinRevenueExt extends CoinRevenue {
        @Relation(parentColumn = "coinDbId", entityColumn = "coinId" ) 
        List<Coin> coins;

        public Coin getCoin() {
            return coins.get(0);
        }

}

And Dao is simple as that

@Query("select * from coinRevenue")
public Flowable<List<CoinRevenueExt>> getAllCoinsRevenueWithCoin();
Cyb answered 28/8, 2018 at 14:48 Comment(4)
Can you give me more information about how to use relation, the DAO is simple but what columns contains? Please explain me moreSchizomycete
@JuniorUsca google docs are pretty good in describing Relation developer.android.com/reference/android/arch/persistence/room/…Cyb
you can use embededed with prefix instead of worrying about column with same name.Clavier
it seems using prefix will cause this issue: #48537365Clavier
M
0

It's a little hard to tell what you are really trying to achieve. Also, your naming is a little odd. It seems that the coin table really contains the currency information. The coinRevenueNew is a ledger entry or order. If you pick easier to follow examples, more people will try to finish reading your posts.

Also, the problem that you are trying to solve is a little unclear. - Is your problem modelling it in the database? - Is your problem that you want to have all amounts automatically updated when the currency changes? - Is your problem that you want to have the objects in memory updated when the database changes? - Is your problem with using foreign keys with Room?

The first issue with modelling has been hinted on by other people. You use a foreign key. There are plenty of articles about it.

Using a little more understandable domain names, you'd have two tables like these:

create table currency (id integer primary key, exchange_rate float);
create table order (id integer primary key, int total, int currency_id, foreign key(currency_id) references currency(id));

You'd create Room entities for each. You'd create a third class (do not mark it with @Entity) that combines both. You can use the annotations @Embedded or @Relation here. The documentation explains this further:

https://developer.android.com/reference/androidx/room/Relation.html

If you change the currency, the storage system will not automatically update all the order totals. If you have a field of "total_in_foreign_currency" and a field of "total_in_master_currency", the database will not recalculate for you. You have to manually iterate over each row and recalculate it.

In memory data objects won't magically update. You have to keep track of when you retrieved the data and if it is still now. You can use LiveData to be notified whenever the data changes (but it won't magically update your objects).

Maffick answered 6/1, 2018 at 2:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.