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 :
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
DTOnotDTO?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
isin awhenstatement 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: