27

I have just started working with Room and although everything seems to be pretty intuitive I currently don't really understand how exactly I could handle relationships.

Because SQLite is a relational database, you can specify relationships between objects. Even though most ORM libraries allow entity objects to reference each other, Room explicitly forbids this. Even though you cannot use direct relationships, Room still allows you to define Foreign Key constraints between entities.(Source: https://developer.android.com/topic/libraries/architecture/room.html#no-object-references)

  1. How should you model a Many to Many or One to Many Relationship?
  2. What would this look like in practice (example DAOs + Entities)?
Rene Ferrari
  • 4,096
  • 3
  • 22
  • 28
  • 5
    With Room, rather than the POJOs modeling database tables, the POJOs model query result sets. This is akin to Web service API clients (e.g., Retrofit, Apollo-Android), which do not model the underlying Web service's database, but rather model the results that you are querying. Hence, with Room, there are no relations, because a query result does not have a relation. – CommonsWare May 19 '17 at 12:21
  • 2
    So basically a "regular query result" is then converted to a POJO? And therefore there is no relationship on a POJO level? Did I understand it correctly? And thanks for your comment! – Rene Ferrari May 19 '17 at 12:37
  • 4
    "Did I understand it correctly?" -- yes, that is how I am interpreting Room. They cover this point in [the Google I|O 2017 presentation on Room](https://www.youtube.com/watch?v=MfHsPGQ6bgE), towards the end. – CommonsWare May 19 '17 at 12:59
  • 1
    Again thank you! By the way amazing book - must check it out some time! – Rene Ferrari May 19 '17 at 13:21

2 Answers2

28

You can use @Relation annotation to handle relations at Room.

A convenience annotation which can be used in a Pojo to automatically fetch relation entities. When the Pojo is returned from a query, all of its relations are also fetched by Room.

See document.

(Google's document has confusing examples. I have written the steps and some basic explanation at my another answer. You can check it out)

Devrim
  • 15,345
  • 4
  • 66
  • 74
  • 2
    Thanks that really helps a lot! When they presented Room at the Google I/O I thought everything is gonna be really simple. Now after seeing how Relationships are handled I think it will confuse you very easily since so many classes have to be created (confusion will probably start if you have 5+ tables and many relationships between them haha). – Rene Ferrari Jun 10 '17 at 11:11
  • 1
    @ReneFerrari: I think the complexity is in the code in order to have better performance and no landmines when running the app. Lazy loading is great for coding but it is bad because you never know in which thread the database will get queried. You can skip frames or worse get ANRs just by lazy loading a field in a list view. – Benoit Duffez Aug 19 '17 at 06:29
  • How can you handle the following with room: I have a Player, Game and a PlayerInGame entity. Now I want to load the Game containing a list of PlayerInGame objects and each PlayerInGame object should also contain the Player. Is that possible with room? – julienduchow Mar 12 '19 at 18:17
-6

I created a simple Convenience Method that populates manually a one to many relationship. So for example if you have a one to many between Country and City , you can use the method to manually populate the cityList property in Country.

/**
 * @param tableOne The table that contains the PK. We are not using annotations right now so the pk should be exposed via a getter getId();
 * @param tableTwo The table that contains the FK. We are not using annotations right now so the Fk should be exposed via a getter get{TableOneName}Id(); eg. getCountryId();
 * @param <T1>     Table One Type
 * @param <T2>     Table Two Type
 * @throws NoSuchFieldException
 * @throws IllegalAccessException
 * @throws NoSuchMethodException
 * @throws InvocationTargetException
 */
private static <T1, T2> void oneToMany(List<T1> tableOne, List<T2> tableTwo) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {

    String tableOneName = tableOne.get(0).getClass().getSimpleName();
    String tableTwoName = tableTwo.get(0).getClass().getSimpleName();
    for (T1 t1 :
            tableOne) {
        Method method = t1.getClass().getMethod("getId");
        Integer pkId = (Integer) method.invoke(t1);
        List<T2> listForCurrentId = new ArrayList<>();
        for (T2 t2 : tableTwo) {
            Method fkMethod = t2.getClass().getDeclaredMethod("get".concat(tableOneName).concat("Id"));
            Integer fkId = (Integer) fkMethod.invoke(t2);
            if (pkId == fkId) {
                listForCurrentId.add(t2);
            }
        }
        Method tableTwoList = t1.getClass().getMethod("set".concat(tableTwoName).concat("List"), List.class);
        tableTwoList.invoke(t1, listForCurrentId);
    }
}

This is how I use it .

   SystemDefaults systemDefaults = new SystemDefaults();
    return Single.zip(systemDao.getRoles(), systemDao.getCountries(), systemDao.getCities(), (roles, countries, cities) -> {
        systemDefaults.setRoles(roles);
        *ConvenienceMethods.oneToMany(countries,cities);*
        systemDefaults.setCountries(countries);
        return systemDefaults;
    });
Muhammad Ahmed AbuTalib
  • 4,080
  • 4
  • 36
  • 59