Alin
Alin

Reputation: 14581

Get LiveData from a many-to-many structure in android Room Architecture Component

Let's take a basic example

A table to store the users

@Entity (tableName="users") 
class UsersEntity(
     @PrimaryKey val id
     var name:String,
      ...
) 

A table to store the roles

@Entity (tableName="roles") 
class RolesEntity(
     @PrimaryKey val id
     var name:String,
      ...
) 

A table to store the many to many relation between users and roles

@Entity (tableName="roles") 
class UserRoles(
     @PrimaryKey val id
     var userId:String,
     var roleId:String
) 

The pojo class I need in my View

class user(
    var id:String,
    var name:String,
     .... other fields
     var roles: List<Role>
)

In my ViewModel how can I pass a user result as LiveData having also the List<Role> filled?

Looking at a general way, I could:

Upvotes: 12

Views: 1589

Answers (2)

Dhagz
Dhagz

Reputation: 741

Create a different model with the User and its Roles, and use @Embedded and @Relation annotations.

Take for example:

public class UserModel {
  @Embedded
  UserEntity user;
  @Relation(parentColumn = "id", entityColumn = "userId", entity = UserRoles.class)
  List<UserRoleModel> userRoles;
}

public class UserRoleModel {
  @Embedded
  UserRoles userRole;
  @Relation(parentColumn = "roleId", entityColumn = "id")
  List<RoleEntity> roles; // Only 1 item, but Room wants it to be a list.
}

You can use the UserModel from here.

Upvotes: 3

Sergei Bubenshchikov
Sergei Bubenshchikov

Reputation: 5371

It's ok to have multiple different data flow in one screen.
On the one hand we can talk about changing user roles list without changing user itself on the other hand user name can be changed without updating roles list. Addition benefit of using multiple data flow, you can show user data while loading user roles.
I suppose, you have pojo of user and roles to avoid synchronization issues. You can implement smooth data delivering (from db to view) like in sample below:


View model

public class UserRolesViewModel extends ViewModel {
    private final MutableLiveData<Integer> mSelectedUser;
    private final LiveData<UsersEntity> mUserData;
    private final LiveData<List<RolesEntity>> mRolesData;
    private DataBase mDatabase;

    public UserRolesViewModel() {
        mSelectedUser = new MutableLiveData<>();
        // create data flow for user and roles synchronized by mSelectedUser 
        mUserData = Transformations.switchMap(mSelectedUser, mDatabase.getUserDao()::getUser);
        mRolesData = Transformations.switchMap(mSelectedUser, mDatabase.getRolesDao()::getUserRoles);
    }

    public void setDatabase(DataBase dataBase) {
        mDatabase = dataBase;
    }

    @MainThread
    public void setSelectedUser(Integer userId) {
        if (mDatabase == null)
            throw new IllegalStateException("You need setup database before select user");
        mSelectedUser.setValue(userId);
    }

    public LiveData<UsersEntity> getUserData() {
        return mUserData;
    }

    public LiveData<List<RolesEntity>> getRolesData() {
        return mRolesData;
    }
}

It's better to encapsulate data source implementation in Repository class and inject it via DI like in this paragraph.

Database sample based on Many-to-Many paragraph from this article


Entities

Users

@Entity(tableName = "users")
public class UsersEntity {
    @PrimaryKey
    public int id;
    public String name;
}

Roles

@Entity(tableName = "roles")
public class RolesEntity {
    @PrimaryKey
    public int id;
    public String name;
}

User roles

This entity require special attention because we need to declare foreign keys to make joun operations futire

@Entity(tableName = "user_roles", primaryKeys = {"user_id", "role_id"}, foreignKeys = {
    @ForeignKey(entity = UsersEntity.class, parentColumns = "id", childColumns = "user_id"),
    @ForeignKey(entity = RolesEntity.class, parentColumns = "id", childColumns = "role_id")
})
public class UserRolesEntity {
    @ColumnInfo(name = "user_id")
    public int userId;
    @ColumnInfo(name = "role_id")
    public int roleId;
}

Dao

Users dao

@Dao
public interface UserDao {
    @Query("SELECT * from users WHERE id = :userId")
    LiveData<UsersEntity> getUser(int userId);
}

Roles dao

@Dao
public interface RolesDao {
    @Query("SELECT * FROM roles INNER JOIN user_roles ON roles.id=user_roles.role_id WHERE user_roles.user_id = :userId")
    LiveData<List<RolesEntity>> getUserRoles(int userId);
}

Data base

@Database(entities = {UsersEntity.class, RolesEntity.class, UserRolesEntity.class}, version = 1)
public abstract class DataBase extends RoomDatabase {
    public abstract UserDao getUserDao();
    public abstract RolesDao getRolesDao();
}

Upvotes: 1

Related Questions