androidandroid-roomsealed-classkotlin-sealed

[Android][Room] Storing Sealed classes into Room Databse


There is no default support for inserting or retreiving kotlin sealed classes into the ROOM database. Enums could be easily work, but not SEALED classes. Below is my example for a DOWNLOADSTATUS seal

@Entity(tableName = "SomeTableName")
data class LibraryDownloadsEntity(
    @PrimaryKey
    val contentId: String,
    val contentURI: String? = EMPTY_STRING,
    val downloadDate: LocalDateTime? = null,
    val downloadStatus: LibraryEntityDownloadStatus = LibraryEntityDownloadStatus.NOTDOWNLOADED()
)

// This is an sealed class
sealed class ShareLibraryEntityDownloadStatus {
    class DOWNLOADING( val downloadProgress: Int) :
        ShareLibraryEntityDownloadStatus()

    class DOWNLOADED(val filePath: String) :
        ShareLibraryEntityDownloadStatus()

    class FAILED(val failureReason: DownloadFailureReason) :
        ShareLibraryEntityDownloadStatus()

    class NOTDOWNLOADED(): ShareLibraryEntityDownloadStatus()
}

Upon using it I will get compile time error as below Cannot figure out how to save this field into database. You can consider adding a type converter for it. - downloadStatus in ... package

Now I used a typeconverter as below

    // Converters for DownloadStatus
    @TypeConverter
    fun downloadStatusToString(downloadStatus: ShareLibraryEntityDownloadStatus): String {
        return Gson().toJson(downloadStatus)
    }

    @TypeConverter
    fun downloadStatusFromString(json: String): ShareLibraryEntityDownloadStatus {
        return Gson().fromJson(json, ShareLibraryEntityDownloadStatus::class.java)
    }

This should work but then you will rember this error on runtime : java.lang.RuntimeException: Failed to invoke private "...somepackagename".downloads.LibraryEntityDownloadStatus() with no args

This make sense that sealed class can't be instantiated. Any idea on how to store downloadStatus information into room db


Solution

  • I had some time put into it and it's a correct beahvior that sealed class can't be instantiated. Solution works on idea of child of sealed class can be instantiated. Typeconverter were successful to get the data from DB (database will pass it to type converter), problem occurs at this point.

            return Gson().fromJson(json, LibraryEntityDownloadStatus::class.java)
    

    LibraryEntityDownloadStatus can't be instantiated by Gson library. However it can instantiate the child of sealed class if that information is available. So I put a let's say a field named 'tag' (should be unique to each child) that can be used to identify child type.

    sealed class ShareLibraryEntityDownloadStatus(val tag: String) {
    class DOWNLOADING(val downloadProgress: Int) :
        ShareLibraryEntityDownloadStatus(tag = "DOWNLOADING")
    
    class DOWNLOADED(val filePath: String) :
        ShareLibraryEntityDownloadStatus(tag = "DOWNLOADED")
    
    class FAILED(val failureReason: DownloadFailureReason) :
        ShareLibraryEntityDownloadStatus(tag = "FAILED")
    
    class NOTDOWNLOADED : ShareLibraryEntityDownloadStatus(tag = "NOTDOWNLOADED")}
    

    And make changes to type converter like this.

    @TypeConverter
    fun downloadStatusToString(downloadStatus: LibraryEntityDownloadStatus): String {
        return Gson().toJson(downloadStatus)
    }
    
    @TypeConverter
    fun downloadStatusFromString(json: String): LibraryEntityDownloadStatus {
        var output : LibraryEntityDownloadStatus = LibraryEntityDownloadStatus.NOTDOWNLOADED()
        val obj = JsonParser.parseString(json).asJsonObject
        when(obj["tag"].asString) {
            "NOTDOWNLOADED" -> output = LibraryEntityDownloadStatus.NOTDOWNLOADED()
            "DOWNLOADING" -> output = Gson().fromJson(json, LibraryEntityDownloadStatus.DOWNLOADING::class.java)
            "DOWNLOADED" -> output = Gson().fromJson(json, LibraryEntityDownloadStatus.DOWNLOADED::class.java)
            "DOWNLOADFAILED" -> output = Gson().fromJson(json, LibraryEntityDownloadStatus.FAILED::class.java)
        }
        return output
    }
    

    JsonParser and Gson is from "package com.google.gson;"

    This apprently worked and we know for a good reason why it does. Is it best way? Let me know what works better.