Android Room,如何保存其中一个变量为密封类对象的实体

7tofc5zh  于 2022-11-03  发布在  Android
关注(0)|答案(2)|浏览(120)

我想在我的Room数据库中保存一个对象,其中一个变量可以是on类型,也可以是其他类型。我认为一个sealed类是有意义的,所以我采用了这种方法:

sealed class BluetoothMessageType() {
    data class Dbm(
        val data: String
    ) : BluetoothMessageType()

    data class Pwm(
        val data: String
    ) : BluetoothMessageType()
}

或者甚至是这个,但这是没有必要的。我发现这个给了我更多的错误,因为它不知道如何处理开放的瓦尔,所以如果我找到了第一个版本的解决方案,我会很高兴无论如何。

sealed class BluetoothMessageType(
    open val data: String
) {
    data class Dbm(
        override val data: String
    ) : BluetoothMessageType()

    data class Pwm(
        override val data: String
    ) : BluetoothMessageType()
}

然后是Entity类

@Entity(tableName = MESSAGES_TABLE_NAME)
data class DatabaseBluetoothMessage(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0L,
    val time: Long = Instant().millis,
    val data: BluetoothMessageType
)

我已经创建了一个TypeConverter来将它与String进行转换,所以我认为这不是一个问题。
首先,这是可能的吗?我假设它应该以类似于抽象类的方式工作,但我也没有设法找到一个工作的解决方案。如果这是不可能的,当我想保存一些数据时,如果不是用密封类,我应该采取什么样的方法?

lsmd5eda

lsmd5eda1#

当我们尝试在我们的领域中使用多态性时,我们遇到了这样的问题,我们这样解决了它:

网域:

我们有一个Photo模型,如下所示:

sealed interface Photo {
    val id: Long

    data class Empty(
        override val id: Long
    ) : Photo

    data class Simple(
        override val id: Long,
        val hasStickers: Boolean,
        val accessHash: Long,
        val fileReferenceBase64: String,
        val date: Int,
        val sizes: List<PhotoSize>,
        val dcId: Int
    ) : Photo
}

Photo内部包含PhotoSize,看起来如下:

sealed interface PhotoSize {
    val type: String

    data class Empty(
        override val type: String
    ) : PhotoSize

    data class Simple(
        override val type: String,
        val location: FileLocation,
        val width: Int,
        val height: Int,
        val size: Int,
    ) : PhotoSize

    data class Cached(
        override val type: String,
        val location: FileLocation,
        val width: Int,
        val height: Int,
        val bytesBase64: String,
    ) : PhotoSize

    data class Stripped(
        override val type: String,
        val bytesBase64: String,
    ) : PhotoSize
}

数据:

要实现这一点,我们的数据模块中有很多工作要做。为了使它看起来更简单,我将把这个过程分解为三个部分:

1.实体:

因此,一般使用Room和SQL,很难保存这样的对象,所以我们不得不提出这个想法。我们的PhotoEntity(这是我们域中Photo的本地版本,看起来像这样:

@Entity
data class PhotoEntity(
    // Shared columns
    @PrimaryKey
    val id: Long,
    val type: Type,

    // Simple Columns
    val hasStickers: Boolean? = null,
    val accessHash: Long? = null,
    val fileReferenceBase64: String? = null,
    val date: Int? = null,
    val dcId: Int? = null
) {
    enum class Type {
        EMPTY,
        SIMPLE,
    }
}

我们的PhotoSizeEntity如下所示:

@Entity
data class PhotoSizeEntity(
    // Shared columns
    @PrimaryKey
    @Embedded
    val identity: Identity,
    val type: Type,

    // Simple columns
    @Embedded
    val locationLocal: LocalFileLocation? = null,
    val width: Int? = null,
    val height: Int? = null,
    val size: Int? = null,

    // Cached and Stripped columns
    val bytesBase64: String? = null,
) {
    data class Identity(
        val photoId: Long,
        val sizeType: String
    )

    enum class Type {
        EMPTY,
        SIMPLE,
        CACHED,
        STRIPPED
    }
}

然后,我们使用这个复合类将PhotoEntityPhotoSizeEntity合并在一起,这样我们就可以检索域模型所需的所有数据:

data class PhotoCompound(
    @Embedded
    val photo: PhotoEntity,
    @Relation(entity = PhotoSizeEntity::class, parentColumn = "id", entityColumn = "photoId")
    val sizes: List<PhotoSizeEntity>? = null,
)

2.刀

因此我们的dao应该能够存储和检索这些数据。为了灵活起见,您可以有两个daos(用于PhotoEntityPhotoSizeEntity),而不是一个,但在本例中,我们将使用一个共享的daos,如下所示:

@Dao
interface IPhotoDao {

    @Transaction
    @Query("SELECT * FROM PhotoEntity WHERE id = :id")
    suspend fun getPhotoCompound(id: Long): PhotoCompound

    @Transaction
    suspend fun insertOrUpdateCompound(compound: PhotoCompound) {
        compound.sizes?.let { sizes ->
            insertOrUpdate(sizes)
        }

        insertOrUpdate(compound.photo)
    }

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertOrUpdate(entity: PhotoEntity)

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertOrUpdate(entities: List<PhotoSizeEntity>)
}

3.适配器:

在解决了将数据保存到SQL数据库的问题之后,我们现在需要解决域和本地实体之间的转换问题,我们的Photo的转换器aka适配器看起来像这样:

fun Photo.toCompound() = when(this) {
    is Photo.Empty -> this.toCompound()
    is Photo.Simple -> this.toCompound()
}

fun PhotoCompound.toModel() = when (photo.type) {
    PhotoEntity.Type.EMPTY -> Photo.Empty(photo.id)
    PhotoEntity.Type.SIMPLE -> this.toSimpleModel()
}

private fun PhotoCompound.toSimpleModel() = photo.run {
    Photo.Simple(
        id,
        hasStickers!!,
        accessHash!!,
        fileReferenceBase64!!,
        date!!,
        sizes?.toModels()!!,
        dcId!!
    )
}

private fun Photo.Empty.toCompound(): PhotoCompound {
    val photo = PhotoEntity(
        id,
        PhotoEntity.Type.EMPTY
    )

    return PhotoCompound(photo)
}

private fun Photo.Simple.toCompound(): PhotoCompound {
    val photo = PhotoEntity(
        id,
        PhotoEntity.Type.SIMPLE,
        hasStickers = hasStickers,
        accessHash = accessHash,
        fileReferenceBase64 = fileReferenceBase64,
        date = date,
        dcId = dcId,
    )

    val sizeEntities = sizes.toEntities(id)
    return PhotoCompound(photo, sizeEntities)
}

对于PhotoSize,它看起来像这样:

fun List<PhotoSize>.toEntities(photoId: Long) = map { photoSize ->
    photoSize.toEntity(photoId)
}

fun PhotoSize.toEntity(photoId: Long) = when(this) {
    is PhotoSize.Cached -> this.toEntity(photoId)
    is PhotoSize.Empty -> this.toEntity(photoId)
    is PhotoSize.Simple -> this.toEntity(photoId)
    is PhotoSize.Stripped -> this.toEntity(photoId)
}

fun List<PhotoSizeEntity>.toModels() = map { photoSizeEntity ->
    photoSizeEntity.toModel()
}

fun PhotoSizeEntity.toModel() = when(type) {
    PhotoSizeEntity.Type.EMPTY -> this.toEmptyModel()
    PhotoSizeEntity.Type.SIMPLE -> this.toSimpleModel()
    PhotoSizeEntity.Type.CACHED -> this.toCachedModel()
    PhotoSizeEntity.Type.STRIPPED -> this.toStrippedModel()
}

private fun PhotoSizeEntity.toEmptyModel() = PhotoSize.Empty(identity.sizeType)

private fun PhotoSizeEntity.toCachedModel() = PhotoSize.Cached(
    identity.sizeType,
    locationLocal?.toModel()!!,
    width!!,
    height!!,
    bytesBase64!!
)

private fun PhotoSizeEntity.toSimpleModel() = PhotoSize.Simple(
    identity.sizeType,
    locationLocal?.toModel()!!,
    width!!,
    height!!,
    size!!
)

private fun PhotoSizeEntity.toStrippedModel() = PhotoSize.Stripped(
    identity.sizeType,
    bytesBase64!!
)

private fun PhotoSize.Cached.toEntity(photoId: Long) = PhotoSizeEntity(
    PhotoSizeEntity.Identity(photoId, type),
    PhotoSizeEntity.Type.CACHED,
    locationLocal = location.toEntity(),
    width = width,
    height = height,
    bytesBase64 = bytesBase64
)

private fun PhotoSize.Simple.toEntity(photoId: Long) = PhotoSizeEntity(
    PhotoSizeEntity.Identity(photoId, type),
    PhotoSizeEntity.Type.SIMPLE,
    locationLocal = location.toEntity(),
    width = width,
    height = height,
    size = size
)

private fun PhotoSize.Stripped.toEntity(photoId: Long) = PhotoSizeEntity(
    PhotoSizeEntity.Identity(photoId, type),
    PhotoSizeEntity.Type.STRIPPED,
    bytesBase64 = bytesBase64
)

private fun PhotoSize.Empty.toEntity(photoId: Long) = PhotoSizeEntity(
    PhotoSizeEntity.Identity(photoId, type),
    PhotoSizeEntity.Type.EMPTY
)

就是这样!

结论:

要将一个密封类保存到Room或SQL中,无论是作为Entity还是作为Embedded对象,您都需要有一个大数据类,其中包含所有密封变量的所有属性,并使用Enum类型来指示变量类型,以便以后在域和数据之间进行转换时使用,或者如果您不使用Clean Architecture,则在代码中进行指示。但是坚实而灵活。我希望Room将有一些注解,可以生成这样的代码,以摆脱样板代码。
PS:这个类取自Telegram的scheme,他们也解决了与服务器通信时的多态性问题。在这里查看他们的TL语言:https://core.telegram.org/mtproto/TL
PS2:如果你喜欢Telegram的TL语言,你可以使用这个生成器从scheme.tl文件生成Kotlin类:https://github.com/tamimattafi/mtproto

**EDIT:**您可以使用此代码生成库自动为复合类生成Dao,使其更易于插入,从而删除了大量的样板文件以正确Map内容。链接:https://github.com/tamimattafi/android-room-compound

快乐编码!

11dmarpk

11dmarpk2#

在我的案例中,我做了以下操作:

sealed class Status2() {
object Online : Status2()
object Offline : Status2()

override fun toString(): String {
    return when (this) {
       is Online ->"Online"
        is Offline -> "Offline"
    }
  }
}

class StatusConverter{
@TypeConverter
fun toHealth(value: Boolean): Status2 {
    return if (value){
        Status2.Online
    } else{
        Status2.Offline
    }
}

@TypeConverter
fun fromHealth(value: Status2):Boolean {
    return when(value){
        is Status2.Offline -> false
        is Status2.Online -> true
    }
  }
}

@Dao
interface CourierDao2 {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertStatus(courier: CourierCurrentStatus)

@Query("SELECT * FROM CourierCurrentStatus")
fun getCourierStatus(): Flow<CourierCurrentStatus>
}

@Entity
 data class CourierCurrentStatus(
 @PrimaryKey
 val id: Int = 0,
 var status: Status2 = Status2.Offline
)

它就像一个符咒

相关问题