【问题标题】:Different accounts with different content, managing user preferences不同的账户有不同的内容,管理用户偏好
【发布时间】:2023-01-26 06:48:36
【问题描述】:

在底部,我添加了一张我当前应用程序结构的图片以及包含的数据类/实体的当前代码。

目前在我的应用程序中,用户在登录片段中插入 url 和代码,单击保存按钮开始获取令牌的请求。成功后,令牌将传递给其他请求以获取类别数据。然后,我从响应中获得的不同类别显示在回收视图中。通过单击一个类别,用户可以按流派片段进入电影/连续剧,我有另一个带有电影或连续剧列表的回收视图。

当令牌请求成功时,url 和代码也被发送到一个名为 AccountData 的数据类(实体),另外还有一个唯一的字符串,将 url 和代码放在一起,用作主键。 AccountData 显示在 Account Managment Fragment 的 recyclerview 中,这是应用程序的开始屏幕。 现在我想让用户为每个帐户选择他想要显示的类别。有可能随时修改他的偏好。 例如:

账户有10个电影类别,用户想看的只有5个了。 帐户有 15 个电影类别,用户只想要显示其中的 6 个。

我的想法是创建一个新的 Fragment,MovieCategorySelectFragment 左右,用户可以在其中单击他想要的类别,将选定的类别传递给 Movies Categories Fragment,就像收藏夹列表一样。为了实现这一点,我想到了 Room。 所以我将 MovieCategory 数据类作为一个实体,使用“Id”作为主键,然后,考虑到它是一对多关系(我希望我对此是正确的),我将 AccountData 实体中的主键添加到电影类别实体。 我使 String 可为空 -> val accountData: String?,我没有收到 NullpointerException 错误。

但现在我卡住了,创建一个新的数据类/实体,称之为 f.e. 会更好吗? SelectedMovieCategory,并将选定的项目/类别(来自 MovieCategorySelectFragment,它不是数据库的一部分)传递给它并使用房间数据库,然后在电影类别片段中显示选择类别。或者我应该提出类别请求并立即将它们保存在房间数据库中,然后处理选择过程?

最后,在这两种方法中,如何将主键从 AccountData 传递到 MovieCategory?否则他们之间没有关系?我是否必须在 Dao 中创建一个函数来处理这个问题?

在账户管理片段的最后,用户应该能够点击他想要加载的账户,为每个账户只加载他之前选择的类别。能够更改他进入 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
)

【问题讨论】:

    标签: android database kotlin android-recyclerview android-room


    【解决方案1】:

    看来您需要帐户、电影类别、系列类别以及一种将帐户与许多电影类别和系列类别相关联/关联的方法,并且能够根据可以更改的帐户首选项来限制列出的电影类别的数量。

    解决方案是帐户和电影类别(可能还有系列类别)之间的多对多关系。

    然而,在继续之前你说: -

    所以我将 MovieCategory 数据类作为一个实体,使用“Id”作为主键,然后,考虑到它是一对多关系(我希望我对此是正确的),我将 AccountData 实体中的主键添加到电影类别实体。

    关于你的希望。我相信你错了。有 3 种类型的关系:-

    1. 1-1 其中表中的每一行都有唯一的方法ID在其他表中通常通过单个列来确定行(如果一个单独的表是合适的(单个表可能就足够了))。 1-1 关系通常不能通过使用表格来满足。

    2. 一对多,其中父表(帐户)中的一行可以有很多子项(电影类别),但电影类别只能有 1 个父项。在这种情况下,电影类别中的一列将包含一个唯一的值ID确定父母。

    3. many-many 一个扩展的 1-M,允许每一边与另一边的任意数量相关。因此,一个帐户可以关联许多其他帐户可以关联的电影类别。典型的解决方案是有一个包含 2 个核心列的中间表。一个存储独特价值的人ID确定关系的一侧和存储唯一值的另一侧ID实体化另一方。通常这 2 列用于主键。

      • 这样的中间表有很多术语来描述这样的表,比如关联表,映射表,引用表......
      • 注意如何ID已突出显示。只需创建一个名为ID在表中(实体)不建立关系,它只支持建立关系的可能性。

      您的问题似乎会勾选多对多关系的复选框,因此会勾选额外的表(如果是帐户安全类别则为 2)。

      该表将有一列用于唯一标识 accountData 行的值(总账户数据).

      • 因为 totalAccountData 是主键(即用@PrimaryKey 注释)并且 PrimaryKey 是隐式唯一的

      该表将有第二列用于 movieCategory 的ID柱子。

      所以你可以开始

      @Entity
      data class AccountMovieMap(
          val accountDataMap: String,
          val movieCategoryMap: String
      ) 
      

      但是,没有哪个房间需要 PrimaryKey,但 @PrimaryKey 注释仅适用于单个列。如果使用其中任何一个,那么由于隐含的唯一性,关系将被限制为一对多。需要一个复合(多列/值)主键,它根据组合值形成唯一性。要在 Room 中指定复合主键,使用 @Entity 注释的 primaryKeys 参数。

      所以 AccountMovieMap 变成:-

      @Entity(
          primaryKeys = ["accountDataMap","movieCategoryMap"]
      )
      data class AccountMovieMap(
          val accountDataMap: String,
          val movieCategoryMap: String
      )
      

      就目前而言,上述内容存在潜在问题,因为可以将数据插入到一个或两个列中,而这些列不是相应表中的值。也就是说,在这种情况下,关系的完整性是不存在的。

      SQLite 和 Room(与许多关系数据库一样)适合强制执行参照完整性. SQLite 通过 ForeignKey 子句来做到这一点。 Room 使用@Entity 注释的foreignKeys 参数来提供ForeignKeys 的列表。

      • 除了强制引用完整性外,SQlite 有 2 个子句 ON DELETE 和 ON UPDATE 有助于维护引用完整性(取决于指定的操作,最有用的是 CASCADE,它允许通过将对父项的更改应用到子项来破坏引用完整性的更改).

      • 如果索引在它认为应该存在的地方不存在,Room 也会发出警告,例如warning: movieCategoryMap column references a foreign key but it is not part of an index. This may trigger full table scans whenever parent table is modified so you are highly advised to create an index that covers this column. 因此,@ColumnInfo 注释可用于在 movieCategoryMap 列上添加索引。

      所以账户电影地图可能更完整:-

      @Entity(
          primaryKeys = ["accountDataMap","movieCategoryMap"]
          , foreignKeys = [
              ForeignKey(
                  entity = AccountData::class,
                  parentColumns = ["totalAccountData"],
                  childColumns = ["accountDataMap"],
                  /* Optional but helps to maintain Referential Integrity */
                  onDelete = ForeignKey.CASCADE,
                  onUpdate = ForeignKey.CASCADE
              ),
              ForeignKey(
                  entity = MovieCategory::class,
                  parentColumns = ["id"],
                  childColumns = ["movieCategoryMap"],
                  onDelete = ForeignKey.CASCADE,
                  onUpdate = ForeignKey.CASCADE
              )
          ]
      )
      data class AccountMovieMap(
          val accountDataMap: String,
          @ColumnInfo(index = true)
          val movieCategoryMap: String
      )
      

      要添加(插入)行,您可以拥有/使用(在 @Dao 注释类中):-

      @Insert(onConflict = OnConflictStrategy.IGNORE)
      fun insert(accountMovieMap: AccountMovieMap)
      
      • 请注意,为了避免引用完整性冲突,引用/映射的帐户数据和引用/映射的电影类别需要存在。

      正如您想要提取 AccountData 的 MovieCategories 一样,您需要一个 POJO,其中包含带有 MovieCategory 列表的 AccountData。

      这可能是:-

      data class AccountWithMovieCategoryList(
          @Embedded
          val accountData: AccountData,
          @Relation(
              entity = MovieCategory::class,
              parentColumn = "totalAccountData", /* The column referenced in the @Embedded */ 
              entityColumn = "id", /* The column referenced in the @Relation (MovieCategory) */
              /* The mapping table */
              associateBy = (
                      Junction(
                          value = AccountMovieMap::class, /* The @Entity annotated class for the mapping table */
                          parentColumn = "accountDataMap", /* the column in the mapping table that references the @Embedded */
                          entityColumn = "movieCategoryMap" /* the column in the mapping table that references the @Relation */
                      )
                      )
          )
          val movieCategoryList: List<MovieCategory>
      )
      

      以下可能是 @Dao 注释接口中的函数,该接口检索给定帐户的 AccountWithMovieCategoryList:-

      @Transaction
      @Query("SELECT * FROM accountData WHERE totalAccountData=:totalAccountData")
      fun getAnAccountWithMovieCategoryList(totalAccountData: String): List<AccountWithMovieCategoryList>
      

      然而Room 将检索所有 MovieCategories,但您希望能够指定一个限制为帐户编辑了 MovieCategories 的数量,因此需要一种方法来覆盖 Room 获取所有映射/关联对象的方法。

      为了促进这一点,可以使用一个带有主体的函数来 a) 获取相应的 AccountData 和 b) 然后通过指定了 LIMIT 的映射表根据帐户获取 MovieCategory 列表。因此 2 个 @Query 函数执行 2 被总体函数调用。

      所以要获取 AccountData:-

      @Query("SELECT * FROM accountData WHERE totalAccountData=:totalAccountData")
      fun getSingleAccount(totalAccountData: String): AccountData
      

      然后通过(加入)映射表获取 AccountData 的有限 MovieCategories :-

      @Query("SELECT movieCategory.* FROM accountMovieMap JOIN movieCategory ON accountMovieMap.MovieCategoryMap = movieCategory.id WHERE accountMovieMap.accountDataMap=:totalAccountData LIMIT :limit")
      fun getLimitedMovieCategoriesForAnAccount(totalAccountData: String,limit: Int): List<MovieCategory>
      

      把它们放在一起,也就是总体功能:-

      @Transaction
      @Query("")
      fun getAccountWithLimitedMovieCategoryList(totalAccountData: String,categoryLimit: Int): AccountWithMovieCategoryList {
          return AccountWithMovieCategoryList(
              getSingleAccount(totalAccountData),
              getLimitedMovieCategoriesForAnAccount(totalAccountData,categoryLimit)
          )
      }
      
      • 请注意,上面的代码只是经过编译(因此 Room 处理没有问题),因此它是原理代码

      • 你说最好,这是有意见的,并不是最好的方法,因为更好的方法是利用 SQLite 对 INTEGER 主键的更有效处理。

    【讨论】:

      猜你喜欢
      • 2011-12-13
      • 1970-01-01
      • 2020-11-03
      • 2022-06-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-20
      • 2014-05-22
      相关资源
      最近更新 更多