Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • have UserDao.getUserById(id) which returns LiveData from the users table and RoleDao.getRolesForUserId(id) which returns LiveData with a list of roles for a user. Then in my fragment, I can do viewModel.getUserById().observe{} and viewModel.getRolesForUserId().observe{}. But this basically means having 2 observers and I'm pretty confident that it's not the way to go.
  • probably other way would be to be able to mix them somehow in my repository or viewmodel so it returns what I need. I'll look into MediatorLiveData
like image 421
Alin Avatar asked Jul 31 '18 13:07

Alin


1 Answers

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();
}
like image 60
Sergey Bubenshchikov Avatar answered Oct 18 '22 03:10

Sergey Bubenshchikov