多账户电影类别偏好管理的Room数据库实现方案咨询
问题描述
现有应用流程
- 用户在Login Fragment输入url和code,点击保存后发起获取token请求,成功后携带token请求电影类别数据,类别展示在RecyclerView中,点击类别进入对应影视列表Fragment。
- token请求成功后,url与code存入
AccountData实体类(以url+code拼接的totalAccountData作为主键),该实体数据展示在启动页Account Management Fragment的RecyclerView中。
需求
实现用户为每个账户自定义显示的电影类别,且可随时修改偏好(如AccountA仅显示10个类别中的5个,AccountB仅显示15个中的6个)。
当前思路与困境
已新增MovieCategorySelectFragment供用户选择类别,计划用Room实现,将MovieCategory设为Entity(以id为主键,添加可空的accountData字段关联AccountData主键),认为二者是一对多关系,但遇到以下问题:
- 是新建
SelectedMovieCategory实体类存储选中类别,还是先将请求到的类别存入Room再处理选择逻辑? - 两种方案下,如何传递
AccountData的主键到MovieCategory建立关联?是否需在Dao中编写对应函数?
最终期望
在Account Management Fragment点击账户时,仅加载该账户之前选中的类别,且可进入MovieCategorySelectFragment修改偏好。
现有数据类代码
data class MovieCategoryResponse( val js: List<MovieCategory> ) @Entity @Parcelize data class MovieCategory( @PrimaryKey(autoGenerate = false) val id: String, val number: Int, val title: String, val accountData: String? ) : Parcelable @Entity data class AccountData( val url: String, val code: String, @PrimaryKey(autoGenerate = false) val totalAccountData: String )
解决方案
推荐方案:新建关联实体存储选中关系
直接在MovieCategory中添加accountData字段会导致数据冗余(同一个类别会被多个账户重复存储),更合理的方式是新建关联实体记录账户与选中类别的映射关系:
1. 实体类调整
- 简化
MovieCategory实体,移除accountData字段(类别属于全局数据,不绑定单个账户):
@Entity @Parcelize data class MovieCategory( @PrimaryKey(autoGenerate = false) val id: String, val number: Int, val title: String ) : Parcelable
- 新建
SelectedMovieCategory关联实体,用复合主键避免重复选中:
@Entity(primaryKeys = ["accountId", "categoryId"]) data class SelectedMovieCategory( val accountId: String, // 对应AccountData的totalAccountData val categoryId: String // 对应MovieCategory的id )
AccountData实体保持不变。
2. 数据存储流程
- 首次获取类别:请求到
MovieCategoryResponse后,用insertOrReplace将所有类别插入Room,避免重复存储。 - 用户选择/修改类别:
- 获取当前账户的
totalAccountData; - 修改偏好时,先删除该账户对应的所有
SelectedMovieCategory记录; - 将用户选中的类别id与账户id组合成
SelectedMovieCategory对象,批量插入Room。
- 获取当前账户的
3. Dao层核心函数
需要编写关联查询、插入、删除的Dao函数:
@Dao interface MovieCategoryDao { // 插入/更新电影类别 @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertCategories(categories: List<MovieCategory>) // 根据账户id查询选中的类别 @Query("SELECT mc.* FROM MovieCategory mc JOIN SelectedMovieCategory smc ON mc.id = smc.categoryId WHERE smc.accountId = :accountId") suspend fun getSelectedCategoriesByAccount(accountId: String): List<MovieCategory> // 删除指定账户的所有选中类别记录 @Query("DELETE FROM SelectedMovieCategory WHERE accountId = :accountId") suspend fun deleteSelectedCategoriesByAccount(accountId: String) // 插入选中的类别关联记录 @Insert suspend fun insertSelectedCategories(selected: List<SelectedMovieCategory>) }
4. 页面逻辑衔接
- Account Management Fragment点击账户:调用
getSelectedCategoriesByAccount查询该账户的选中类别,传入电影类别Fragment展示。 - 进入MovieCategorySelectFragment修改偏好:
- 查询该账户已选中的类别id列表,用于页面标记已选状态;
- 查询Room中所有可用电影类别;
- 用户确认选择后,先删除旧的关联记录,再插入新的选中关联数据。
备选方案:复用MovieCategory添加选中标记(不推荐)
如果坚持复用现有MovieCategory结构,可添加选中状态字段,但会存在数据冗余问题:
- 修改
MovieCategory实体:
@Entity @Parcelize data class MovieCategory( @PrimaryKey(autoGenerate = false) val id: String, val number: Int, val title: String, val accountData: String, // 改为非空,绑定单个账户 val isSelected: Boolean ) : Parcelable
- Dao核心函数:
@Dao interface MovieCategoryDao { // 插入账户专属类别数据 @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAccountCategories(categories: List<MovieCategory>) // 查询账户选中的类别 @Query("SELECT * FROM MovieCategory WHERE accountData = :accountId AND isSelected = 1") suspend fun getSelectedCategoriesByAccount(accountId: String): List<MovieCategory> // 更新类别选中状态 @Query("UPDATE MovieCategory SET isSelected = :isSelected WHERE id = :categoryId AND accountData = :accountId") suspend fun updateCategorySelection(accountId: String, categoryId: String, isSelected: Boolean) }
- 流程:首次获取类别时,为每个类别设置对应
accountData和默认isSelected=false,插入Room;用户选择时调用updateCategorySelection修改状态。但同一个类别会被每个账户存储一次,数据冗余严重,不推荐。
内容的提问来源于stack exchange,提问作者Alex Mutschl
相关产品推荐
相关产品推荐

