How to use Kotlin generics to access same fields in sibling classes - kotlin

data class Type1(val str: String)
data class Type2(val str: String)
interface Person
data class Child1(val name: Type1) : Person
data class Child2(val name: Type2) : Person
fun main() {
val foo = Child1(Type1("foo"))
val bar = Child2(Type2("bar"))
printName(foo)
printName(bar)
}
fun printName(person: Person) {
// Option 1: would like to do this
// println(person.name) // Unresolved reference: name
// Option 2: works but if I have lots of other code,
// it's unnecessary duplication of code
when (person) {
is Child1 -> {
println(person.name)
// lots of other code
}
is Child2 -> {
println(person.name)
// lots of other code
}
}
}
Inside printName(), I would like to be able to use a single println(person.name) call and have it print the name of either Child1 or Child2, whichever is passed in. What are the different ways I can make this happen in Kotlin?
UPDATE:
I don't own the base classes so not able to change the inheritance.
The names of each child are of different types.

You need to have name in your interface, like
interface Person {
val name: String
}
data class Child1(override val name: String) : Person
data class Child2(override val name: String) : Person
to be able to get it from other Child classes...
If not every child class that extends Person will contain name, you can introduce an intermediate interface, like
interface Person
interface NamedPerson : Person {
val name: String
}
data class Child1(override val name: String) : NamedPerson
data class Child2(override val name: String) : NamedPerson
In any other case, there should be an instance check, like
when (person) {
is Child1 -> println(person.name)
is Child2 -> println(person.name)
}

You can create an extension function if you don't own those classes
data class Type1(val str: String)
data class Type2(val str: String)
interface Person
data class Child1(val name: Type1) : Person
data class Child2(val name: Type2) : Person
// extension
fun Person.getNameStr() : String {
return when(this){
is Child1 -> name.str
is Child2 -> name.str
else -> ""
}
}
// or
val Person.nameStr : String
get() = when(this){
is Child1 -> name.str
is Child2 -> name.str
else -> ""
}
fun printName(person: Person) {
println(person.getNameStr())
//or
println(person.nameStr)
}
Importance
this extension will not valid if you want to return the class itself (Type1 and Type2)

Related

Kotlin - make multiple sealed classes have common set of "base" subclasses, but each could still add it's specific ones

This question has a wider scope than Extract common objects from sealed class in kotlin and Android - How to make sealed class extend other sealed class? so it's not a duplicate of these
I have multiple sealed classes that represent results of various API calls. Each of these calls has a common set of expected results (success, network error, unexpected error), but each could introduce it's own result types (like 'user not found' or 'wrong ID').
To avoid copying same subclasses to each of sealed class, I want to create a "base" type that includes all common result types, while each sealed class could add it's specific subclasses:
interface BaseApiCallResult {
data class Success(val data: String) : BaseApiCallResult
data class UnexpectedError(val error: Throwable) : BaseApiCallResult
data class NetworkError(val error: ApolloException) : BaseApiCallResult
}
sealed class ApiCallResult1 : BaseApiCallResult {
data class WrongID(val id: Int) : ApiCallResult1()
}
sealed class ApiCallResult2 : BaseApiCallResult {
data class UserDoesNotExist(val userid: Long) : ApiCallResult2()
}
sealed class ApiCallResult3 : BaseApiCallResult {
data class NameAlreadyTaken(val name: String) : ApiCallResult3()
}
the problem is that subclasses in "base" cannot be treated as "child" classes:
fun apiCall1(): ApiCallResult1 {
// won't compile, since BaseApiCallResult.UnexpectedError is not ApiCallResult1
return BaseApiCallResult.UnexpectedError(Exception(""))
}
fun useApi() {
when(val result = apiCall1()) {
is ApiCallResult1.WrongID -> { }
// compile error: Incompatible types
is BaseApiCallResult.Success -> { }
is BaseApiCallResult.UnexpectedError -> { }
is BaseApiCallResult.NetworkError -> { }
}
}
solution from Android - How to make sealed class extend other sealed class? might be applied here, but for big number of sealed classes (I expect I might need several dozen of such classes) it becomes rather hacky
interface BaseApiCallResult {
data class Success(val data: String) : Everything
data class UnexpectedError(val error: Throwable) : Everything
data class NetworkError(val error: ApolloException) : Everything
}
sealed interface ApiCallResult1 : BaseApiCallResult {
data class WrongID(val id: Int) : ApiCallResult1()
}
sealed interface ApiCallResult2 : BaseApiCallResult {
data class UserDoesNotExist(val userid: Long) : ApiCallResult2
}
sealed interface ApiCallResult3 : BaseApiCallResult {
data class NameAlreadyTaken(val name: String) : ApiCallResult3
}
// adding each new sealed interface here seems like a hack
interface Everything : BaseApiCallResult, ApiCallResult1, ApiCallResult2, ApiCallResult3
Additionally, with above solution, every when {...} complains about Everything case not being handled. I could resign from using Everything, but then I have to list all interfaces in each "base" subclass, which is simply terrible:
// just imagine how would it look if there were 30 ApiCallResult classes
interface BaseApiCallResult {
data class Success(val data: String) : BaseApiCallResult, ApiCallResult1, ApiCallResult2, ApiCallResult3
data class UnexpectedError(val error: Throwable) : BaseApiCallResult, ApiCallResult1, ApiCallResult2, ApiCallResult3
data class NetworkError(val error: ApolloException) : BaseApiCallResult, ApiCallResult1, ApiCallResult2, ApiCallResult3
}
Is there a better way to handle this kind of situation ?
You have to separate ApiResult from ApiMethodResult they should not to be relatives.
Kotlin already has type Result and you can use it:
sealed interface ApiCall1Result {
class WrongID : ApiCall1Result
class UserInfo(val userId: Int) : ApiCall1Result
}
fun api1() : Result<ApiCallResult>
fun useApi1() {
val result = api1()
if(result.isFailure) {
handle failure
} else {
val apiResult = result.getOrThrow()
when(apiResult) {
is WrongID -> {}
is UserInfo -> {}
}
}
}
Or you can implement it by your self:
interface ApiResult<in T> {
class Success<T : Any>(val data: T) : ApiResult<T>
class Fail(val error: Throwable) : ApiResult<Any>
}
sealed class ApiCallResult1 {
class WrongID(val id: Int) : ApiCallResult1()
class UserInfo(val id: Int, val name: String) : ApiCallResult1()
}
fun apiCall1(): ApiResult<ApiCallResult1> {
return ApiResult.Fail(Throwable())
}
fun useApi() {
when (val result = apiCall1()) {
is ApiResult.Fail -> {}
is ApiResult.Success -> when (result.data) {
is ApiCallResult1.WrongID -> {}
is ApiCallResult1.UserInfo -> {}
}
}
}
You could create a generic type for the sealed interface, and this type gets wrapped by one additional child class:
interface ApiCallResult<out O> {
data class Success(val data: String) : ApiCallResult<Nothing>
data class UnexpectedError(val error: Throwable) : ApiCallResult<Nothing>
data class NetworkError(val error: ApolloException) : ApiCallResult<Nothing>
data class Other<out O>(val value: O): ApiCallResult<O>
}
Then you can define your other callback types using a specific class as the O type:
data class UserDoesNotExist(val userid: Long)
fun handleApiCallResult2(result: ApiCallResult<UserDoesNotExist>) {
when (result) {
is ApiCallResult.Success -> {}
is ApiCallResult.UnexpectedError -> {}
is ApiCallResult.NetworkError -> {}
is ApiCallResult.Other -> {
// do something with result.value
}
}
}
When you have more than one other case, you can create a sealed interface to be the parent of those other cases, but you'll unfortunately need a nested when to handle them.
When you have no other cases, you can use ApiCallResult<Nothing> as your response type, but you'll unfortunately need to leave a do-nothing {} branch for the Other case. Or you could set up a separate sealed interface like in your long-winded final solution in your question, which is manageable because it would never grow to more than two sealed types.

How do I create an instance of a Class passed as parameter to a function?

I'm building a library in Kotlin and here's my usecase
I have a base class
abstract class Component(...) {
// ... class body
}
I want the users of my library to define their own sub-classes like say:
class MyComponent() : Component() {
// .. class body
}
How can I write a helper function that takes in this derived class as a param and create an instance out of it. Something like:
fun helper(component: Class, props: HashMap<String, String>) : Component {
// somehow create a new instance of Class and return it?
}
Thanks!
You can have users pass a constructor reference:
fun helper(componentConstructor: ()->Component, props: Map<String, String>) : Component {
val component = componentConstructor()
// set it up and return it.
}
// usage:
val component = helper(::MyComponent, emptyMap())
Better for props not to require a specific type of map since it doesn’t matter here. Needless burden for users of your library.
abstract class Component(val prop1: String, val prop2: String) {
// ... class body
}
class MyComponent(prop1: String, prop2: String) : Component (prop1, prop2) {
// ... class body
}
fun helper(component: Class<MyComponent>, props: Map<String, String>): Component {
val constructor = component.constructors.first { it.parameterCount == props.size }
val arguments = props.values.toTypedArray()
return constructor.newInstance(*arguments) as Component
}
val instance = helper(MyComponent::class.java, mapOf("prop1" to "value1", "prop2" to "value2"))
println(instance.prop1 + ", " + instance.prop2) // Prints: value1, value2

enum polymorphism implementation

Consider an interface:
interface class IPage {
}
and two enums implementing this interface as
enum class Page1 : IPage{ ..... }
enum class Page2 : IPage{ ..... }
Now I have a method
fun getPage(isSomeCondition : Boolean) : IPage{
if(isSomeCondition) return Page1
else return Page2
}
However I am getting a compile time error:
Classifier Page1 does not have a companion object and must be initialized here, I believe that is happening because of the interface, but I am out of ideas on how to solve it!
More explanation:
Both of these enums hold values which are similar, basis the boolean condition I want to load either of the enums and work on the values contained inside of that enum.
consider for example my enums with an updated signature:
enum Page1(val title: String, val data : Data)
Now my Data class has various implementations like ChildData | ParentData | FriendData etc
example:
enum Page1(val title: String, val data : Data){
PARENT("Heiachi", ParentData(...)),
CHILD("JIN", ChildData(...))
}
enum Page2(val title: String, val data : Data){
PARENT("Hworang", FriendData(...)),
CHILD("Yoshimitsu", FoeData(....))
}
Where
class ParentData : Data
class ChildData : Data
class FriendData : Data
class FoeData : Data
Maybe you want to iterate the children of the enum that you've been returning? If so, you can return the implementations of the enums, not the enum definitions:
fun getPage(isSomeCondition : Boolean) : Iterable<IPage>{
if(isSomeCondition) return Page1.values()
else return Page2.values()
}
Alternatively, you can make a class that defines a parent or child. You can then optionally store thes in a single enum or just make them conform to another interface.
class PageType(val title: String, val data : Data)
enum Page(val parent: PageType, val child: PageType) {
Page1(PageType("Heiachi", ParentData(...)), PageType(("JIN", ChildData(...))),
Page2(PageType("Hworang", FriendData(...)), PageType("Yoshimitsu", FoeData(....))
}
fun getPage(isSomeCondition : Boolean) : Page {
if(isSomeCondition) return Page.Page1
else return Page.Page2
}
// Alternative to enum:
interface Page {
val parent: PageType
val child: PageType
}
object Page1: Page {
override val parent = PageType("Heiachi", ParentData(...))
override val child = PageType(("JIN", ChildData(...))
}
object Page2: Page {
override val parent = PageType("Hworang", FriendData(...))
override val child = PageType(("Hworang", FoeData(...))
}

merging different models into sealed class

I’m writing PC app using Kotlin and TornadoFX.
I’ll show you what I have
FooModel.kt:
class FooModel(val id: Int){
constructor(foo: Foo) : this(foo.id)
}
Foo.kt:
data class Foo(val id: Int)
MainController.kt:
val FoosList: ObservableList<FooModel> = FXCollections.observableArrayList<FooModel>()
//bellow I wanna describe what I’m already doing with val above
fun fooDifferentOperationsForExample(example: Int){
example1Object = FoosList.find{ it.id == example }
example2Object.removeIf { it.id == example }
example3Object.setAll(it.map {FooModel})
}
I’m showing described model in listview and want to add objects of another kind(s) in the same list. I have a solution with sealed class, example of DifferentKindsOfInstances.kt:
sealed class DifferentKindsOfInstances{
data class Foo(val foo: FooModel): DiffrentKindsOfInstances()
data class Bar(val bar: BarModel): DiffrentKindsOfInstances()
}
let’s think BarModel is exact copy of FooModel, nevermind.
But if I wanna continue working with FooModel in a way I worked before(in MainControl) - how would the code look then?
it.foo.id doesn’t work for me
Here's a solution that references the sealed class members in two places: rendering the ListCell in cellFragment{} and in the binding of the selected sealed class item to the textfield string content.
class ItemModel1 ( val dataField1 : String )
class ItemModel2 ( val dataField2 : String )
sealed class ItemViewInstance {
data class Item1(val item1: ItemModel1): ItemViewInstance()
data class Item2(val item2: ItemModel2): ItemViewInstance()
}
class SealedClassDemoView : View("Sealed Class") {
val instances = listOf(
ItemViewInstance.Item1(ItemModel1("One")),
ItemViewInstance.Item2(ItemModel2("Two"))
).observable()
val selectedItemString = SimpleStringProperty()
override val root = vbox {
listview(instances) {
cellFormat {
when( it ) {
is ItemViewInstance.Item1 -> text = it.item1.dataField1
is ItemViewInstance.Item2 -> text = it.item2.dataField2
}
}
selectedItemString.bind(
Bindings.createStringBinding(
Callable {
val sel = selectionModel.selectedItemProperty().value
when (sel) {
is ItemViewInstance.Item1 -> sel.item1.dataField1
is ItemViewInstance.Item2 -> sel.item2.dataField2
else -> ""
}
},
selectionModel.selectedItemProperty()
)
)
}
textfield(selectedItemString)
padding = Insets(2.0)
spacing = 4.0
}
}
class SealedClassDemoApp : App(SealedClassDemoView::class)
fun main(args: Array<String>) {
launch<SealedClassDemoApp>(args)
}

How to obtain all subclasses of a given sealed class?

Recently we upgraded one of our enum class to sealed class with objects as sub-classes so we can make another tier of abstraction to simplify code. However we can no longer get all possible subclasses through Enum.values() function, which is bad because we heavily rely on that functionality. Is there a way to retrieve such information with reflection or any other tool?
PS: Adding them to a array manually is unacceptable. There are currently 45 of them, and there are plans to add more.
This is how our sealed class looks like:
sealed class State
object StateA: State()
object StateB: State()
object StateC: State()
....// 42 more
If there is an values collection, it will be in this shape:
val VALUES = setOf(StateA, StateB, StateC, StateC, StateD, StateE,
StateF, StateG, StateH, StateI, StateJ, StateK, StateL, ......
Naturally no one wants to maintain such a monster.
In Kotlin 1.3+ you can use sealedSubclasses.
In prior versions, if you nest the subclasses in your base class then you can use nestedClasses:
Base::class.nestedClasses
If you nest other classes within your base class then you'll need to add filtering. e.g.:
Base::class.nestedClasses.filter { it.isFinal && it.isSubclassOf(Base::class) }
Note that this gives you the subclasses and not the instances of those subclasses (unlike Enum.values()).
With your particular example, if all of your nested classes in State are your object states then you can use the following to get all of the instances (like Enum.values()):
State::class.nestedClasses.map { it.objectInstance as State }
And if you want to get really fancy you can even extend Enum<E: Enum<E>> and create your own class hierarchy from it to your concrete objects using reflection. e.g.:
sealed class State(name: String, ordinal: Int) : Enum<State>(name, ordinal) {
companion object {
#JvmStatic private val map = State::class.nestedClasses
.filter { klass -> klass.isSubclassOf(State::class) }
.map { klass -> klass.objectInstance }
.filterIsInstance<State>()
.associateBy { value -> value.name }
#JvmStatic fun valueOf(value: String) = requireNotNull(map[value]) {
"No enum constant ${State::class.java.name}.$value"
}
#JvmStatic fun values() = map.values.toTypedArray()
}
abstract class VanillaState(name: String, ordinal: Int) : State(name, ordinal)
abstract class ChocolateState(name: String, ordinal: Int) : State(name, ordinal)
object StateA : VanillaState("StateA", 0)
object StateB : VanillaState("StateB", 1)
object StateC : ChocolateState("StateC", 2)
}
This makes it so that you can call the following just like with any other Enum:
State.valueOf("StateB")
State.values()
enumValueOf<State>("StateC")
enumValues<State>()
UPDATE
Extending Enum directly is no longer supported in Kotlin. See
Disallow to explicitly extend Enum class : KT-7773.
With Kotlin 1.3+ you can use reflection to list all sealed sub-classes without having to use nested classes: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-class/sealed-subclasses.html
I asked for some feature to achieve the same without reflection: https://discuss.kotlinlang.org/t/list-of-sealed-class-objects/10087
Full example:
sealed class State{
companion object {
fun find(state: State) =
State::class.sealedSubclasses
.map { it.objectInstance as State}
.firstOrNull { it == state }
.let {
when (it) {
null -> UNKNOWN
else -> it
}
}
}
object StateA: State()
object StateB: State()
object StateC: State()
object UNKNOWN: State()
}
A wise choice is using ServiceLoader in kotlin. and then write some providers to get a common class, enum, object or data class instance. for example:
val provides = ServiceLoader.load(YourSealedClassProvider.class).iterator();
val subInstances = providers.flatMap{it.get()};
fun YourSealedClassProvider.get():List<SealedClass>{/*todo*/};
the hierarchy as below:
Provider SealedClass
^ ^
| |
-------------- --------------
| | | |
EnumProvider ObjectProvider ObjectClass EnumClass
| |-------------------^ ^
| <uses> |
|-------------------------------------------|
<uses>
Another option, is more complicated, but it can meet your needs since sealed classes in the same package. let me tell you how to archive in this way:
get the URL of your sealed class, e.g: ClassLoader.getResource("com/xxx/app/YourSealedClass.class")
scan all jar entry/directory files in parent of sealed class URL, e.g: jar://**/com/xxx/app or file://**/com/xxx/app, and then find out all the "com/xxx/app/*.class" files/entries.
load filtered classes by using ClassLoader.loadClass(eachClassName)
check the loaded class whether is a subclass of your sealed class
decide how to get the subclass instance, e.g: Enum.values(), object.INSTANCE.
return all of instances of the founded sealed classes
If you want use it at child class try this.
open class BaseSealedClass(val value: String, val name: Int) {
companion object {
inline fun<reified T:BaseSealedClass> valueOf(value: String): T? {
return T::class.nestedClasses
.filter { clazz -> clazz.isSubclassOf(T::class) }
.map { clazz -> clazz.objectInstance }
.filterIsInstance<T>()
.associateBy { it.value }[value]
}
inline fun<reified T:BaseSealedClass> values():List<T> =
T::class.nestedClasses
.filter { clazz -> clazz.isSubclassOf(T::class) }
.map { clazz -> clazz.objectInstance }
.filterIsInstance<T>()
}
}
#Stable
sealed class Theme(value: String, name: Int): BaseSealedClass(value, name) {
object Auto: Theme(value = "auto", name = R.string.setting_general_theme_auto)
object Light: Theme(value= "light", name = R.string.setting_general_theme_light)
object Dark: Theme(value= "dark", name = R.string.setting_general_theme_dark)
companion object {
fun valueOf(value: String): Theme? = BaseSealedClass.valueOf(value)
fun values():List<Theme> = BaseSealedClass.values()
}
}
For a solution without reflection this is a library that supports generating a list of types to sealed classes at compile time:
https://github.com/livefront/sealed-enum
The example in the docs
sealed class Alpha {
object Beta : Alpha()
object Gamma : Alpha()
#GenSealedEnum
companion object
}
will generate the following object:
object AlphaSealedEnum : SealedEnum<Alpha> {
override val values: List<Alpha> = listOf(
Alpha.Beta,
Alpha.Gamma
)
override fun ordinalOf(obj: Alpha): Int = when (obj) {
Alpha.Beta -> 0
Alpha.Gamma -> 1
}
override fun nameOf(obj: AlphaSealedEnum): String = when (obj) {
Alpha.Beta -> "Alpha_Beta"
Alpha.Gamma -> "Alpha_Gamma"
}
override fun valueOf(name: String): AlphaSealedEnum = when (name) {
"Alpha_Beta" -> Alpha.Beta
"Alpha_Gamma" -> Alpha.Gamma
else -> throw IllegalArgumentException("""No sealed enum constant $name""")
}
}
The short version is
State::class.sealedSubclasses.mapNotNull { it.objectInstance }