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
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.