I'm trying to implement caching of a JSON API response with Room.
The response I get in JSON follows this data class structure:

@Serializable
data class ApiDataResponse(
    val success: Boolean,
    val message: String? = null,
    val albums: List<AlbumResponse> = emptyList()
)
@Serializable
data class AlbumResponse(
    val id: String,
    val title: String,
    val createdBy: String,
    val enabled: Boolean,
    val keywords: List<String>,
    val pics: List<PicResponse>
)
@Serializable
data class PicResponse(
    val picUrl: String,
    val emojis: List<String>
)

Notes:

  • @Serializable是从kotlinx.serialization库中解析出的JSON响应.
  • 这些响应数据类只在My datasource layer中使用,view layer不关心ApiDataResponse,只知道AlbumResponse的"纯"版本AlbumPicResponse的"纯"版本Pic(所谓"纯",我指的是没有外部库注释的数据类).

因此,要用Room实现这个缓存,我可以丢弃ApiDataResponse,只保存AlumResponse(以及PicResponse)的内容,按照这个 idea 拥有Room entities个新的数据类:

@Entity(tableName = "albums")
data class AlbumEntity(
    @PrimaryKey(autoGenerate = false)
    val id: String,
    val title: String,
    val createdBy: String,
    val enabled: Boolean,
    val keywords: List<String>, // obstacle here
    val pics: List<PicEntity> // obstacle here
)
// obstacle here
// @Entity
data class PicEntity(
    val picUrl: String,
    val emojis: List<String>
)

我已经知道如何在Room中保存简单数据,使用我能够完成这项任务的最简单的JSON,问题是在这个更复杂的场景中,我不知道如何实现这个目标.所以我希望有人能在这种情况下指导我.

推荐答案

我认为问题出在作为列表的列上.

您可以添加以下类,以便将列表嵌入到类中:-

data class StringList(
    val stringList: List<String>
)
data class PicEntityList(
    val picEntityList: List<PicEntity>
)

然后将100更改为使用上述列表,如下所示:

@Entity(tableName = "albums")
data class AlbumEntity(
    @PrimaryKey(autoGenerate = false)
    val id: String,
    val title: String,
    val createdBy: String,
    val enabled: Boolean,
    //val keywords: List<String>, // obstacle here
    val keywords: StringList, /// now not an obstacle
    //val pics: List<PicEntity> // obstacle here
    val emojis: PicEntityList// now not an obstacle
)

为了能够存储"复杂的"(单个对象),您需要对其进行转换,以便一些TypeConverter,例如

class RoomTypeConverters{
    @TypeConverter
    fun convertStringListToJSON(stringList: StringList): String = Gson().toJson(stringList)
    @TypeConverter
    fun convertJSONToStringList(json: String): StringList = Gson().fromJson(json,StringList::class.java)
    @TypeConverter
    fun convertPicEntityListToJSON(picEntityList: PicEntityList): String = Gson().toJson(picEntityList)
    @TypeConverter
    fun convertJSONToPicEntityList(json: String): PicEntityList = Gson().fromJson(json,PicEntityList::class.java)
}
  • 注意,这利用了依赖关系com.google.code.gson

然后,您需要有@TypeConverters注释来覆盖适当的范围(在@数据库级别是最大的范围).Note the plural rather than singular, they are different.

为了演示上述工作,使用@DAO:-注释的接口中的First个函数

@Dao
interface AlbumDao {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(albumEntity: AlbumEntity): Long
    @Query("SELECT * FROM albums")
    fun getAllAlbums(): List<AlbumEntity>
}

Second@数据库注解类(请注意@TypeConverters注解):-

@TypeConverters(RoomTypeConverters::class)
@Database(entities = [AlbumEntity::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
    abstract fun getAlbumDao(): AlbumDao

    companion object {
        @Volatile
        private var instance: TheDatabase?=null
        fun getInstance(context: Context): TheDatabase {
            if (instance==null) {
                instance = Room.databaseBuilder(context,TheDatabase::class.java,"album.db")
                    .allowMainThreadQueries()
                    .build()
            }
            return instance as TheDatabase
        }
    }
}

Third一些实际做某事的活动代码(插入一些相册,然后将提取的数据写入日志(log)):

class MainActivity : AppCompatActivity() {
    lateinit var db: TheDatabase
    lateinit var dao: AlbumDao
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        db = TheDatabase.getInstance(this)
        dao = db.getAlbumDao()

        dao.insert(AlbumEntity(
            "Album001", "The First Album","Fred",false,
            StringList(listOf("The","First","Album")),
            PicEntityList(
                listOf(
                    PicEntity("PE001", listOf("emoji1","emoji2","emoji3")),
                    PicEntity("PE002",listOf("emoji10")),
                    PicEntity("PE003", listOf("emoji20","emoji21"))
                ))
        ))
        dao.insert(AlbumEntity(
            "Album002","This is the Second Album","Mary", true,
            StringList(listOf("keya","keyb","keyc","keyd","keye")),
            PicEntityList(
                listOf(
                    PicEntity("PE011", listOf("emoji30","emoji31")),
                    PicEntity("PE012", listOf("emoji1","emoji10","emoji20","emoji30"))
            ))
        ))
        for (a in dao.getAllAlbums()) {
            logAlbum(a)
        }
    }

    fun logAlbum(albumEntity: AlbumEntity) {
        val keywords = StringBuilder()
        for(s in albumEntity.keywords.stringList) {
            keywords.append("\n\t$s")
        }
        val pelog = StringBuilder()
        for (pe in albumEntity.emojis.picEntityList) {
            pelog.append("\n\tURL is ${pe.picUrl}")
            for (emoji in pe.emojis) {
                pelog.append("\n\t\tEmoji is ${emoji}")
            }
        }
        Log.d(
            "ALBUMINFO",
            "Album id is ${albumEntity.id} " +
                    "Title is ${albumEntity.title} " +
                    "CreateBy ${albumEntity.createdBy} " +
                    "Enabled=${albumEntity.enabled}. " +
                    "It has ${albumEntity.keywords.stringList.size} keywords. " +
                    "They are $keywords\n. " +
                    "It has ${albumEntity.emojis.picEntityList.size} emojis. " +
                    "They are ${pelog}"
        )
    }
}
  • 在主线程上运行,以方便和简洁

运行时,日志(log)包含:-

D/ALBUMINFO: Album id is Album001 Title is The First Album CreateBy Fred Enabled=false. It has 3 keywords. They are 
        The
        First
        Album
    . It has 3 emojis. They are 
        URL is PE001
            Emoji is emoji1
            Emoji is emoji2
            Emoji is emoji3
        URL is PE002
            Emoji is emoji10
        URL is PE003
            Emoji is emoji20
            Emoji is emoji21
            
            
D/ALBUMINFO: Album id is Album002 Title is This is the Second Album CreateBy Mary Enabled=true. It has 5 keywords. They are 
        keya
        keyb
        keyc
        keyd
        keye
    . It has 2 emojis. They are 
        URL is PE011
            Emoji is emoji30
            Emoji is emoji31
        URL is PE012
            Emoji is emoji1
            Emoji is emoji10
            Emoji is emoji20
            Emoji is emoji30
  • 即两张专辑与适当的嵌入列表一起被提取.

唱片表本身(通过应用判断)包括:-

enter image description here

从数据库的Angular 来看,另一种更好的方法不是将列表作为单个值(字符串)嵌入,而是将列表作为相关表(具有一对多或多对多关系).

Android相关问答推荐

编写Inbox需要具有匹配兼容性的Kotlin版本

如何使用Gradle风味在两个Kotlin导入(Google vs Amazon Java billing library)之间进行 Select ?

Jetpack Compose-如何使用值动画直接控制其他动画

Jetpack Compose:如何将浮动操作按钮上方的子按钮居中对齐?

在以XML格式设置完整屏幕视图时可见App Compat按钮

为什么我有多个Player实例?

从片段导航回来

为什么可以';我不能直接在RecyclerView.ViewHolder中访问视图ID吗?

Play store 的 Play 完整性与 Firebase 应用判断 Play 完整性

延时kotlin中时分秒的使用方法

React-native 3D对象渲染

Jetpack Compose 惰性列在单个项目更新时重新组合所有项目

在 Jetpack Compose 中包装内容

如何将一个 Composable 作为其参数传递给另一个 Composable 并在 Jetpack Compose 中显示/运行它

在compose中,为什么修改List元素的属性,LazyColumn不刷新

组合 - 重新组合图像

如何对齐文本和图标可组合,以便即使在文本溢出后它们也能保持在一起?

无法解析依赖项'com.github.smarteist:autoimageslider:1.4.0-appcompat'

我的 react native 项目的发布签名 apk 没有在设备中打开,而且它创建的尺寸非常小

MVVM - 这个逻辑的最佳层是什么?