Kotlin sealed class with data classes not recognizing subclass - kotlin

I am trying to define a Kotlin sealed class which consists of a number of data classes. The latter are used to define data transfer objects (DTO) representing the mySQL tables in a room database. I introduced the sealed class to generalize the different DTOs and be able to refer to them all by their supertype (DTO - the common properties each specific DTO has, eg. "id", etc.).
This compiles alright, but I don't think Kotlin understands that the data classes are the "subclasses" of the sealed class - no matter whether I defined them all in the same file as the sealed (parent) class, or - the preferred choice - in the same package... both options should be valid choices, according to the Kotlin documentation.
Any idea, where I'm going wrong here? Thanks.
Code:
package com.tanfra.shopmob.smob.data.local.dto
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.RewriteQueriesToDropUnusedColumns
import com.tanfra.shopmob.smob.data.local.utils.*
/**
* supertype, common to all DTO types - generic part of any DTO class
* (properties declared abstract --> implementation delegated to inheriting concrete class)
*/
sealed class Dto {
abstract val id: String
abstract var itemStatus: SmobItemStatus
abstract var itemPosition: Long
}
#Entity(tableName = "smobGroups")
#RewriteQueriesToDropUnusedColumns
data class SmobGroupDTO(
#PrimaryKey #ColumnInfo(name = "groupId") override val id: String = "invalid smob group entry",
#ColumnInfo(name = "groupItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
#ColumnInfo(name = "groupItemPosition") override var itemPosition: Long = -1L,
#ColumnInfo(name = "groupName") var name: String = "",
#ColumnInfo(name = "groupDescription") var description: String? = "",
#ColumnInfo(name = "groupType") var type: GroupType = GroupType.OTHER,
#ColumnInfo(name = "groupMembers") var members: List<String> = listOf(),
#ColumnInfo(name = "groupActivityDate") var activityDate: String = "",
#ColumnInfo(name = "groupActivityReps") var activityReps: Long = 0,
) : Dto()
#Entity(tableName = "smobLists")
#RewriteQueriesToDropUnusedColumns
data class SmobListDTO(
#PrimaryKey #ColumnInfo(name = "listId") override val id: String = "invalid smob list id",
#ColumnInfo(name = "listItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
#ColumnInfo(name = "listItemPosition") override var itemPosition: Long = -1L,
#ColumnInfo(name = "listName") var name: String = "",
#ColumnInfo(name = "listDescription") var description: String? = "",
#ColumnInfo(name = "listItems") var items: List<SmobListItem> = listOf(),
#ColumnInfo(name = "listMembers") var members: List<String> = listOf(),
#ColumnInfo(name = "listLifecycleStatus") var lcStatus: SmobItemStatus = SmobItemStatus.OPEN,
#ColumnInfo(name = "listLifecycleCompletion") var lcCompletion: Double = -1.0,
) : Dto()
#Entity(tableName = "smobProducts")
#RewriteQueriesToDropUnusedColumns
data class SmobProductDTO(
#PrimaryKey #ColumnInfo(name = "productId") override val id: String = "invalid smob product id",
#ColumnInfo(name = "productItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
#ColumnInfo(name = "productItemPosition") override var itemPosition: Long = -1L,
#ColumnInfo(name = "productName") var name: String = "",
#ColumnInfo(name = "productDescription") var description: String? = "",
#ColumnInfo(name = "productImageUrl") var imageUrl: String? = "",
#ColumnInfo(name = "productCategoryMain") var categoryMain: ProductMainCategory = ProductMainCategory.OTHER,
#ColumnInfo(name = "productCategorySub") var categorySub: ProductSubCategory = ProductSubCategory.OTHER,
#ColumnInfo(name = "productActivityDate") var activityDate: String = "",
#ColumnInfo(name = "productActivityReps") var activityReps: Long = 0L,
#ColumnInfo(name = "productInShopCategory") var inShopCategory: ShopCategory = ShopCategory.OTHER,
#ColumnInfo(name = "productInShopName") var inShopName: String = "dummy shop",
#ColumnInfo(name = "productInShopLocation") var inShopLocation: ShopLocation = ShopLocation(0.0, 0.0),
) : Dto()
#Entity(tableName = "smobShops")
#RewriteQueriesToDropUnusedColumns
data class SmobShopDTO(
#PrimaryKey #ColumnInfo(name = "shopId") override val id: String = "invalid smob shop id",
#ColumnInfo(name = "shopItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
#ColumnInfo(name = "shopItemPosition") override var itemPosition: Long = -1L,
#ColumnInfo(name = "shopName") var name: String = "",
#ColumnInfo(name = "shopDescription") var description: String? = "",
#ColumnInfo(name = "shopImageUrl") var imageUrl: String? = "",
#ColumnInfo(name = "shopLocationLatitude") var locLat: Double = 0.0,
#ColumnInfo(name = "shopLocationLongitude") var locLong: Double = 0.0,
#ColumnInfo(name = "shopType") var type: ShopType = ShopType.INDIVIDUAL,
#ColumnInfo(name = "shopCategory") var category: ShopCategory = ShopCategory.OTHER,
#ColumnInfo(name = "shopBusiness") var business: List<String> = listOf()
) : Dto()
#Entity(tableName = "smobUsers")
#RewriteQueriesToDropUnusedColumns
data class SmobUserDTO(
#PrimaryKey #ColumnInfo(name = "userId") override val id: String = "invalid smob user id",
#ColumnInfo(name = "userItemStatus") override var itemStatus: SmobItemStatus = SmobItemStatus.NEW,
#ColumnInfo(name = "userItemPosition") override var itemPosition: Long = -1L,
#ColumnInfo(name = "userUsername") var username: String = "",
#ColumnInfo(name = "userName") var name: String = "",
#ColumnInfo(name = "userEmail") var email: String = "",
#ColumnInfo(name = "userImageUrl") var imageUrl: String? = ""
) : Dto()
The reason, I believe Kotlin didn't make the desired connection between the sealed class and the data classes (= subclasses) is that it still asks me for an "else" branch in "when" expressions which act upon the members of the sealed class:
package com.tanfra.shopmob.smob.data.net.nto2dto
import com.tanfra.shopmob.smob.data.local.dto.*
import com.tanfra.shopmob.smob.data.net.nto.*
import com.tanfra.shopmob.smob.data.repo.ato.Ato
// ATO --> DTO
fun <DTO: Dto, ATO: Ato> ATO._asDatabaseModel(d: DTO): DTO? {
return when (d) {
is SmobGroupDTO -> {
SmobGroupDTO(
id = (this as SmobGroupNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
name = this.name,
description = this.description,
type = this.type,
members = this.members,
activityDate = this.activity.date,
activityReps = this.activity.reps,
) as DTO
}
is SmobListDTO -> {
SmobListDTO(
id = (this as SmobListNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
name = this.name,
description = this.description,
items = this.items,
members = this.members,
lcStatus = this.lifecycle.status,
lcCompletion = this.lifecycle.completion,
) as DTO
}
is SmobProductDTO -> {
SmobProductDTO(
id = (this as SmobProductNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
name = this.name,
description = this.description,
imageUrl = this.imageUrl,
categoryMain = this.category.main,
categorySub = this.category.sub,
activityDate = this.activity.date,
activityReps = this.activity.reps,
inShopCategory = this.inShop.category,
inShopName = this.inShop.name,
inShopLocation = this.inShop.location,
) as DTO
}
is SmobShopDTO -> {
SmobShopDTO(
id = (this as SmobShopNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
name = this.name,
description = this.description,
imageUrl = this.imageUrl,
locLat = this.location.latitude,
locLong = this.location.longitude,
type = this.type,
category = this.category,
business = this.business,
) as DTO
}
is SmobUserDTO -> {
SmobUserDTO(
id = (this as SmobUserNTO).id,
itemStatus = this.itemStatus,
itemPosition = this.itemPosition,
username = this.username,
name = this.name,
email = this.email,
imageUrl = this.imageUrl,
) as DTO
}
else -> null
} // when(DTO) ... resolving generic type to concrete type
}

It's caused by your use of generics on the method signature :
fun <DTO: Dto, ATO: Ato> ATO._asDatabaseModel(d: DTO): DTO?
There's a good thread on Reddit which is very like your example. See here:
https://www.reddit.com/r/Kotlin/comments/ei8zh5/kotlin_requires_else_branch_in_when_statement/
So, to solve your problem, just change the method signature to return a type of DTO not DTO?
It's almost as if the compiler is forgetting that the DTO is a sealed class when you make it a generic parameter, so you need an exhaustive check.
As you as using is in a when statement Kotlin will smart cast the DTO to the right type anyway, so no need for the generic argument.
Here's a cut down example based on your code that works without the else:
package paul.sealed
sealed class DTO {
abstract val id: String
}
data class SmobGroupDTO(override val id: String = "invalid smob user id", val name: String = "") : DTO()
data class SmobListDTO(override val id: String = "invalid smob user id", val name: String = "") : DTO()
fun main() {
fun processDTO(dto: DTO): String {
return when (dto) {
is SmobGroupDTO -> "Group"
is SmobListDTO -> "List"
}
}
}

Related

Why is MapStruct warning this is an unmapped property

I am employing mapstruct to map between my network and database data objects within my current Android project.
api 'org.mapstruct:mapstruct:1.5.2.Final'
kapt 'org.mapstruct:mapstruct-processor:1.5.2.Final'
my network data class resembles this
#Keep
#Serializable
data class Item(
#SerialName("actionTested")
val actionTested: String? = null,
#SerialName("agonistAntagonist")
val agonistAntagonist: String = "",
#SerialName("cellOrigin")
val cellOrigin: String? = null,
#SerialName("cmmnt")
val comment: String? = null,
#SerialName("displayDose")
val displayDose: String = "",
#SerialName("displayValue")
val displayValue: String = "",
#SerialName("document")
val document: Document = Document(),
#SerialName("documentYear")
val documentYear: Int = 0,
#SerialName("dose")
val dose: String = "",
#SerialName("doseUnit")
val doseUnit: String? = null,
#SerialName("drug")
val drug: String = "",
#SerialName("errorType")
val errorType: String? = null,
#SerialName("errorValue")
val errorValue: String? = null,
#SerialName("experimentType")
val experimentType: String? = null,
#SerialName("hash")
val hash: String = "",
#SerialName("id")
val id: String = "",
#SerialName("isPrimaryTarget")
val isPrimaryTarget: String? = null,
#SerialName("mutationDetail")
val mutationDetail: String? = null,
#SerialName("otherExpDetails")
val otherExpDetails: String? = null,
#SerialName("parameter")
val parameter: String = "",
#SerialName("sharpN")
val sharpN: String? = null,
#SerialName("smiles")
val smiles: String? = null,
#SerialName("source")
val source: String = "",
#SerialName("specie")
val specie: String = "",
#SerialName("strainRace")
val strainRace: String? = null,
#SerialName("studyType")
val studyType: String = "",
#SerialName("target")
val target: String = "",
#SerialName("targetType")
val targetType: String = "",
#SerialName("testSystem")
val testSystem: String = "",
#SerialName("transfection")
val transfection: String = "",
#SerialName("unitNormalized")
val unitNormalized: String? = null,
#SerialName("unitOriginal")
val unitOriginal: String? = null,
#SerialName("valueNormalized")
val valueNormalized: String = "",
#SerialName("valueOriginal")
val valueOriginal: String = ""
)
My database data object resembles this
data class ActivitySearchItemDO(
#ColumnInfo(name = "action_tested") val actionTested: String?,
#ColumnInfo(name = "agonist_antagonist") val agonistAntagonist: String,
#ColumnInfo(name = "cell_origin") val cellOrigin: String?,
#ColumnInfo(name = "comment") val comment: String?,
#ColumnInfo(name = "display_dose") val displayDose: String,
#ColumnInfo(name = "display_value") val displayValue: String,
#ColumnInfo(name = "document") val document: DocumentVO,
#ColumnInfo(name = "document_year") val documentYear: Int,
#ColumnInfo(name = "dose") val dose: String,
#ColumnInfo(name = "dose_unit") val doseUnit: String?,
#ColumnInfo(name = "drug") val drug: String,
#ColumnInfo(name = "error_type") val errorType: String?,
#ColumnInfo(name = "error_value") val errorValue: String?,
#ColumnInfo(name = "experiment_type") val experimentType: String?,
#ColumnInfo(name = "hash") val hash: String,
#ColumnInfo(name = "id") val id: String,
#ColumnInfo(name = "is_primary_target") val isPrimaryTarget: String? = null,
#ColumnInfo(name = "mutation_detail") val mutationDetail: String?,
#ColumnInfo(name = "other_exp_details") val otherExpDetails: String?,
#ColumnInfo(name = "parameter") val parameter: String,
#ColumnInfo(name = "sharp_N") val sharpN: String?,
#ColumnInfo(name = "smiles") val smiles: String?,
#ColumnInfo(name = "source") val source: String,
#ColumnInfo(name = "specie") val specie: String,
#ColumnInfo(name = "strain_race") val strainRace: String?,
#ColumnInfo(name = "study_type") val studyType: String,
#ColumnInfo(name = "target") val target: String,
#ColumnInfo(name = "target_type") val targetType: String,
#ColumnInfo(name = "test_system") val testSystem: String,
#ColumnInfo(name = "transfection") val transfection: String,
#ColumnInfo(name = "unit_normalized") val unitNormalized: String?,
#ColumnInfo(name = "unit_original") val unitOriginal: String?,
#ColumnInfo(name = "value_normalized") val valueNormalized: String,
#ColumnInfo(name = "value_original") val valueOriginal: String
)
when i build my project i get the following warning and do not understand why
warning: Unmapped target property: "isPrimaryTarget".
public abstract com.myapp.model.database.ActivitySearchItemDO map(#org.jetbrains.annotations.NotNull()
my mapper resembles this
#InheritInverseConfiguration(name = "map")
fun map(dataFields: Item): ActivitySearchItemDO
why is mapstruct reporting that the isPrimaryTarget is not being mapped?
UPDATE
it appears to be the "is" prefix that is causing the issue, when i remove is from the variable names it maps ok. which i am happy with as this field is not a boolean type but a String.
however i now have another case that is boolean and its generating a similar warning. why does mapstruct have an issue when mapping boolean fields.

Room - How to set nullable Foreign Key

In my Kotlin Android codebase I have the following 2 entities..
#Entity(
tableName = ModuleConfiguration.tableName,
primaryKeys = [ModuleConfiguration.COL_ID],
foreignKeys = arrayOf(
ForeignKey(
entity = Module::class,
parentColumns = [Module.COL_ID],
childColumns = [ModuleConfiguration.COL_MODULE_ID],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Group::class,
parentColumns = [Group.COL_ID],
childColumns = [ModuleConfiguration.COL_GROUP_ID]
)
)
)
class ModuleConfiguration(
#ColumnInfo(name = COL_ID)
var id: String = UUID.randomUUID().toString(),
#ColumnInfo(name = COL_TABLE)
var table: String,
#ColumnInfo(name = COL_FIELD_NAME)
var fieldName: String,
#ColumnInfo(name = COL_FIELD_LABEL)
var fieldLabel: String,
#ColumnInfo(name = COL_FIELD_TYPE)
var fieldType: ModuleConfigurationItemType,
#ColumnInfo(name = COL_GROUP_ID)
var groupId: String?,
#ColumnInfo(name = COL_MODULE_ID)
var moduleId: String,
#ColumnInfo(name = COL_POSITION)
var position: Int,
#ColumnInfo(name = COL_VISIBLE)
var visible: Int = 1, //Usually visible
#ColumnInfo(name = COL_READ_ONLY)
var readOnly: Int = 0, //Usually false
#ColumnInfo(name = COL_REQUIRED)
var required: Int = 0, //Usually false
#ColumnInfo(name = COL_CREATED_AT)
var createdAt: Long = CustomDateTimeUtil.getTodayInUTC(),
#ColumnInfo(name = COL_UPDATED_AT)
var updatedAt: Long = CustomDateTimeUtil.getTodayInUTC()
) : Cloneable, Serializable, Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readString() ?: "",
parcel.readString() ?: "",
parcel.readString() ?: "",
fieldType = ModuleConfigurationItemType.valueOf(parcel.readString() ?: FieldType.UNKNOWN.name),
groupId = parcel.readString(),
moduleId = parcel.readString() ?: "",
position = parcel.readInt(),
visible = parcel.readInt(),
readOnly = parcel.readInt(),
required = parcel.readInt(),
createdAt = parcel.readLong(),
updatedAt = parcel.readLong()
) {
}
fun getViewType() : ModuleConfigurationItemType {
return this.fieldType
}
override fun equals(other: Any?): Boolean {
return super.equals(other)
}
override fun hashCode(): Int {
return super.hashCode()
}
override fun clone(): Any {
return super.clone()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(id)
parcel.writeString(table)
parcel.writeString(fieldName)
parcel.writeString(fieldLabel)
parcel.writeString(fieldType.name)
parcel.writeString(groupId)
parcel.writeString(moduleId)
parcel.writeInt(position)
parcel.writeInt(visible)
parcel.writeInt(readOnly)
parcel.writeInt(required)
parcel.writeLong(createdAt)
parcel.writeLong(updatedAt)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<ModuleConfiguration> {
const val tableName = "module_configuration"
const val COL_ID = "id"
const val COL_MODULE_ID = "module_id"
const val COL_TABLE = "table"
const val COL_FIELD_NAME = "field_name"
const val COL_FIELD_LABEL = "field_label"
const val COL_FIELD_TYPE = "field_type"
const val COL_GROUP_ID = "group_id"
const val COL_VISIBLE = "visible"
const val COL_READ_ONLY = "read_only"
const val COL_REQUIRED = "required"
const val COL_POSITION = "position"
const val COL_CREATED_AT = "created_at"
const val COL_UPDATED_AT = "updated_at"
const val COL_CLIENT_ID = "client_id"
override fun createFromParcel(parcel: Parcel): ModuleConfiguration {
return ModuleConfiguration(parcel)
}
override fun newArray(size: Int): Array<ModuleConfiguration?> {
return arrayOfNulls(size)
}
}
}
and Group Entity
#Entity(
tableName = Group.tableName,
primaryKeys = [Group.COL_ID]
)
class Group(
#ColumnInfo(name = COL_ID)
var id: String = UUID.randomUUID().toString(),
#ColumnInfo(name = COL_NAME)
var name: String,
#ColumnInfo(name = COL_CREATED_AT)
var createdAt: Long = CustomDateTimeUtil.getTodayInUTC(),
#ColumnInfo(name = COL_UPDATED_AT)
var updatedAt: Long = CustomDateTimeUtil.getTodayInUTC()
) : Cloneable, Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readString() ?: "",
parcel.readLong(),
parcel.readLong()
) {
}
override fun equals(other: Any?): Boolean {
return super.equals(other)
}
override fun hashCode(): Int {
return super.hashCode()
}
override fun clone(): Any {
return super.clone()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(id)
parcel.writeString(name)
parcel.writeLong(createdAt)
parcel.writeLong(updatedAt)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Group> {
const val tableName = "group"
const val COL_ID = "id"
const val COL_NAME = "name"
const val COL_CREATED_AT = "created_at"
const val COL_UPDATED_AT = "updated_at"
const val COL_CLIENT_ID = "client_id"
override fun createFromParcel(parcel: Parcel): Group {
return Group(parcel)
}
override fun newArray(size: Int): Array<Group?> {
return arrayOfNulls(size)
}
}
}
My problem is that I am trying to set nullable foreign key, as you can see in entity named ModuleConfiguration, there is column named group_id, which is nullable.
#ColumnInfo(name = COL_GROUP_ID)
var groupId: String?,
Since, this column belongs to other entity named Group, I am trying to make it foreign key, but when I do that, I am getting following error
SQLiteConstraintException: FOREIGN KEY constraint failed (code 787)
On searching online, I found following answers on stackoverflow:
Android with Room - How to set a foreign key nullable
or
Nullable foreign key on room
but this did not help me as these answer suggest to change primitive types like int or long into non-primitive types like Integer or Long for allowing them to be null. I am already using String as type.
any help would be highly appreciated.
Thanks in advance.

Room generate id with constructor

I am usind Room DB and I have class which I need to generate id:
#Entity
data class TaskEntity(
#PrimaryKey(autoGenerate = true) var uid: Long?,
#ColumnInfo(name = "title") var title: String?,
#ColumnInfo(name = "start") var startTime: Long?,
#ColumnInfo(name = "duration") var duration: Long?,
): Serializable {
constructor(
) : this(null, "", 0, 0)
}
In the activity I will init the calss and set parameters at runtime:
var task: TaskEntity
task = TaskEntity()
task.name = cal.name
....
Dao class:
#Dao
interface TasksDao {
#Query("SELECT * FROM taskentity ORDER BY startTime ASC")
fun getAllTasks(): List<TaskEntity>
#Insert
fun insert(item: TaskEntity)
#Delete
fun delete(item: TaskEntity)
#Update
fun update(item: TaskEntity)
}
But the ID is always null as in the constructor. I have seen in some documentation in case its null it will be auto generated. What am I missing?
You have manually created the TaskEntity object, so it's uid is null
Also null or 0 initial value is allowed for integer field with primary key and autoGenerate flag
So i guess it is better to write
#PrimaryKey(autoGenerate = true) var uid: Long,
// ...
) : this(0L, "", 0, 0)

Retrofit.responseBodyConverter does not work to convert errorBody() to custom ResponseWrapper

Trying to convert errorBody() of retrofit error response to ResponseWrapper as follows:
retrofit.responseBodyConverter<ResponseWrapper<R>>(Failure::class.java, arrayOf(object : Annotation {}))
.convert(response.errorBody())
ResponseWrapper is as follows:
sealed class ResponseWrapper<R> {
data class Success<R>(val payload: R) : ResponseWrapper<R>()
data class Failure<R>(
#field:Json(name = "code") val code: Int = INVALID_CODE,
#field:Json(name = "status") val status: String = "",
#field:Json(name = "exception") val exception: String = "",
#field:Json(name = "message") val message: String = "",
#field:Json(name = "fieldErrors") val fieldErrors: List<FieldError> = emptyList()
) : ResponseWrapper<R>()
class NotFound<R> : ResponseWrapper<R>()
class NoInternet<R> : ResponseWrapper<R>()
data class FieldError(
#field:Json(name = "field") val field: String,
#field:Json(name = "message") val message: String,
#field:Json(name = "rejectValue") val rejectValue: String
) }
By doing this with Retrofit/Moshi, error message as follows is created:
java.lang.AbstractMethodError: abstract method "java.lang.Class java.lang.annotation.Annotation.annotationType()"
at retrofit2.converter.moshi.MoshiConverterFactory.jsonAnnotations(MoshiConverterFactory.java:122)
at retrofit2.converter.moshi.MoshiConverterFactory.responseBodyConverter(MoshiConverterFactory.java:91)
at retrofit2.Retrofit.nextResponseBodyConverter(Retrofit.java:330)
at retrofit2.Retrofit.responseBodyConverter(Retrofit.java:313)
What did I wrong here?

Android room not returning anything with url as parameter

I'm trying to access a list of Items by the primary key of Feed like so. The primary key is a url. Attempting to access it like this gives me null back, and attepting to put :arg0 in quotes creates a compile time error error: Unused parameter: arg0. Is it possible to use a url for this query?
#Query("SELECT * FROM item WHERE feed_url = :arg0")
fun observeAllByFeed(feedUrl: String): LiveData<List<Item>>
Item
#Entity(foreignKeys = arrayOf(ForeignKey(
entity = Feed::class,
parentColumns = arrayOf("url"),
childColumns = arrayOf("feed_url"))))
data class Item(
#ColumnInfo(name = "guid") #PrimaryKey var guid: String = "",
#ColumnInfo(name = "categoryIds") var categoryIds: List<Int> = emptyList(),
#ColumnInfo(name = "comments") var comments: String? = null,
#ColumnInfo(name = "content") var content: String? = null,
#ColumnInfo(name = "description") var description: String = "",
#ColumnInfo(name = "author") var author: String = "",
#ColumnInfo(name = "block") var block: Boolean = false,
#ColumnInfo(name = "duration") var duration: Int = 0,
#ColumnInfo(name = "explicit") var explicit: String = "",
#ColumnInfo(name = "image_url") var imageUrl: String = "",
#ColumnInfo(name = "keywords") var keywords: List<String> = emptyList(),
#ColumnInfo(name = "subtitle") var subTitle: String = "",
#ColumnInfo(name = "summary") var summary: String = "",
#ColumnInfo(name = "link") var link: String = "",
#ColumnInfo(name = "publication_date") var pubDate: Date = Date(),
#ColumnInfo(name = "title") var title: String = "",
#ColumnInfo(name = "feed_url") var feedURL: String = "",
#ColumnInfo(name = "download_state") var downloadState: Int = Item.DownloadState.STATE_NOT_DOWNLOADED
) : Serializable {
companion object DownloadState {
val STATE_NOT_DOWNLOADED = 0
val STATE_DOWNLOADING = 1
val STATE_DOWNLOADED = 2
}
}
Feed
#Entity(foreignKeys = arrayOf(ForeignKey(
entity = Owner::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("owner_id")
)))
data class Feed(
#ColumnInfo(name = "url") #PrimaryKey var URL: String = "",
#ColumnInfo(name = "copyright") var copyright: String? = null,
#ColumnInfo(name = "description") var description: String? = null,
#ColumnInfo(name = "itemGUIDs") var itemGUIDs: List<String> = emptyList(),
#ColumnInfo(name = "author") var author: String? = null,
#ColumnInfo(name = "block") var block: Boolean = false,
#ColumnInfo(name = "categoryIds") var categoryIds: List<Int> = emptyList(),
#ColumnInfo(name = "explicit") var explicit: Boolean = false,
#ColumnInfo(name = "image_url") var imageUrl: String? = null,
#ColumnInfo(name = "owner_id") var ownerId: Int = -1,
#ColumnInfo(name = "subtitle") var subtitle: String? = null,
#ColumnInfo(name = "summary") var summary: String? = null,
#ColumnInfo(name = "language") var language: String = "",
#ColumnInfo(name = "link") var link: String = "",
#ColumnInfo(name = "title") var title: String = "",
#ColumnInfo(name = "expiry") var expiry: Date = Date(),
#ColumnInfo(name = "subscribed") var subscribed: Boolean = false
) : Serializable
Owner
#Entity
data class Owner(
#ColumnInfo(name = "name") var name: String? = null,
#ColumnInfo(name = "email") var email: String? = null
) : Serializable {
#ColumnInfo(name = "id")
#PrimaryKey(autoGenerate = true)
var id: Int = 0
}
It turns out that when using LiveData with room, it immediately returns and so calling LiveData.value immediately gives null while it asynchronously gets the actual data to provide to Observers, and so can't be used as a drop in replacement if trying to convert from a non LiveData call. This is documented here: https://developer.android.com/reference/android/arch/lifecycle/LiveData.html#getValue()
Returns the current value. Note that calling this method on a background thread does not guarantee that the latest value set will be received.