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:
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.MediatorLiveData
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:
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
@Entity(tableName = "users")
public class UsersEntity {
@PrimaryKey
public int id;
public String name;
}
@Entity(tableName = "roles")
public class RolesEntity {
@PrimaryKey
public int id;
public String name;
}
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
public interface UserDao {
@Query("SELECT * from users WHERE id = :userId")
LiveData<UsersEntity> getUser(int userId);
}
@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);
}
@Database(entities = {UsersEntity.class, RolesEntity.class, UserRolesEntity.class}, version = 1)
public abstract class DataBase extends RoomDatabase {
public abstract UserDao getUserDao();
public abstract RolesDao getRolesDao();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With