Implementing a type-safe class hierarchy w/ a nullable value - kotlin

I (often) have a resource with two states, pre-created and post-created, where both states have the same fields except for an id field. id is null in the pre-created state and non-null in the post-created state.
I would like to define and use this resource in a clean and type-safe way.
It's common to represent this ID field as a nullable, which handles both scenarios with minimal boilerplate in the class definition. The problem is that it creates a lot of boilerplate in the business logic because you can't assert whether a resource is pre-created or post-created by looking at its type.
Here is an example of the nullable approach:
data class Resource(val id: String?, val property: String)
This is simple to define, but not as simple to handle with due to lack of compile-time guarantees.
Here's an example of a more type-safe approach:
sealed class Resource(val property: String) {
class WithoutID(property: String): Resource(property)
class WithID(val id: String, property: String): Resource(property)
}
This allows me to pass around Resource.WithID and Resource.WithoutID, which have all the same fields and methods, except for id.
One inconvenience with this type-safe approach is that the resource definition code gets quite bloated when you have many property fields. This bloating makes the code harder to read.
I'm wondering if there's an alternative approach with less boilerplate, or if Kotlin has any features that make this kind of thing simpler.

What about defining
sealed class MayHaveId<T> { abstract val record: T }
class WithId<T>(val id: String, override val record: T): MayHaveId<T>()
class WithoutId<T>(override val record: T): MayHaveId<T>()
class Resource(val property: String)
// and other similar types
and using WithId<Resource> and WithoutId<Resource>? In Scala you could add an implicit conversion from MayHaveId<T> to T, but not in Kotlin, alas, nor can you write : T by record. Still should be clean enough to use.

One of the options is to get into composition relying on properties inside interfaces.
interface Resource {
val property: String
}
interface WithId : Resource {
val id: Int
}
interface WithOtherField : Resource {
val otherField: Any
}
class WithoutIdImpl(override val property: String) : Resource
class WithIdImpl(override val id: Int, override val property: String) : WithId
class WithIdAndOtherField(
override val id: Int,
override val otherField: Any,
override val property: String) : WithId, WithOtherField
I didn't get from your example, how you're going to switch between two states of Resource. So probably there is a gap to overcome.
Probably, Smart casts will allow to switch states.

Related

Define an Enum structure in Kotlin?

I have seen a lot of examples of interfaces for enums methods here but I am looking for another thing.
I want to assure some string enums have at least three keys there:
enum InterstitialEnum(val webName: string) {
Showed("interstitialShowed"),
Dismissed("interstitialDismissed"),
Failed("interstitialFailed"),
SomeInterstititalValue("intersititalSomeValue")
}
enum VideoEnum(val webName: string) {
Showed("videoShowed"),
Dismissed("videoDismissed"),
Failed("videoFailed"),
VideoSomethingHere("videoSomethingHere")
}
My end goal is to use that interface as function parameter, so I can access functionParameter.Showed.webName, etc.
I tried to create an interface but I can not find a way to define Showed, Dismissed or Failed, just functions.
This does not work
interface BaseEnum {
val FailedToShow: String;
}
Edit:
Important, this is not a duplicate of How to extend enums in Kotlin? because I do not want the same key/value pair, I want the same key with different value.
You can’t do this with different enums because there’s no mechanism for relating the names of enum instances of different enums.
Here’s an idea for something that’s similar to the structure you’re looking for.
interface WebNames {
val showed: String
val dismissed: String
val failed: String
}
object InterstitialWebNames: WebNames {
override val showed: String = "interstitialShowed"
override val dismissed: String = "interstitialDismissed"
override val failed: String = "interstitialFailed"
}
object VideoWebNames: WebNames {
override val showed: String = "videoShowed"
override val dismissed: String = "videoDismissed"
override val failed: String = "videoFailed"
}
If you have other properties, you could use a wrapper class instead of Strings for these properties.
I think #TenFour04's example is the closest you're going to get. You can think of an enum as a type, and Showed, Dismissed and Failed as subtypes - but there's no way of enforcing that a particular supertype must have a certain set of subtypes, with specific names.
If you don't just want to deal with String properties (e.g. so you can do something like if (state is Showed) then you could make a type for that:
open class State(val webName: String)
class Showed(webName: String) : State(webName)
class Dismissed(webName: String) : State(webName)
class Failed(webName: String) : State(webName)
interface WebNames {
val showed: Showed
val dismissed: Dismissed
val failed: Failed
}
object VideoWebNames : WebNames {
override val showed = Showed("videoShowed")
override val dismissed = Dismissed("videoDismissed")
override val failed = Failed("videoDismissed")
// a State that's not a standard one included in the interface
val videoSomethingHere = State("videoSomethingHere")
}
if you wanted you could stick all the required states in a sealed class, to group them together and maybe do some checking later
open class State(val webName: String)
sealed class RequiredState(webName: String) : State(webName)
class Showed(webName: String) : RequiredState(webName)
class Dismissed(webName: String) : RequiredState(webName)
class Failed(webName: String) : RequiredState(webName)
So now videoWebNames.showed is a State that also is Showed and is RequiredState

Is there a way to "disable" the primary constructor or force the user to always use a secondary constructor?

Example:
data class Car (
val type: TypeEnum,
val brand: BrandEnum,
val modelNumber: Int)
{
constructor(val type: TypeEnum,
val brand: BrandEnum,
val input: String) : this (
type,
brand,
Valdidator.validateModelNumber(input)
)
}
In the code above, the method validateModelNumber() validates a raw input and throws an exception if the model number has an invalid format. I want to force the user to use this constructor every time he/she wants to make a Car object.
Essentially: I want to make sure that no invalid Car object can exist, while still making the code as immutable as possible.
You could use the init block instead. Something like this
data class Car (
val type: TypeEnum,
val brand: BrandEnum,
val modelNumber: Int)
{
init {
Valdidator.validateModelNumber(input)
}
}
Using an init block for validation (as per another answer) can work well if it only needs the parameters/properties specified in the primary constructor.  However, there are other approaches.
If you don't want the primary constructor to be used by other code, you can make it private, by changing:
data class Car(
to:
data class Car private constructor(
You could then leave a public secondary constructor for other classes to use, as in the question.  However, that's still a bit limiting, as you can't do any serious processing before calling the primary constructor.
So the usual pattern is to have a private constructor and factory methods in the companion object.  This is much more flexible: you can do any amount of processing before and after calling the actual constructor; you can even return cached instances, subclass instances, etc.
You can make those look like constructors by implementing them as operator fun invoke() with suitable parameters.  In this case, that could look like:
data class Car private constructor(
val type: TypeEnum,
val brand: BrandEnum,
val modelNumber: Int)
{
companion object {
operator fun invoke(type: TypeEnum, brand: BrandEnum, input: String)
= Car(type, brand, Validator.validateModelNumber(input))
}
}
You could then create instances with e.g.:
Car(TypeEnum.SPORTS, BrandEnum.ASTON_MARTIN, "DB5")
looking just like an ordinary constructor.

Kotlin data class secondary constructor init block

Let's imagine that we have data class with two properties and we need secondary constructor for some reasons. Problem is that i need recalculate each argument in primary constructor call instead of using some cached value of raw.split("_"):
data class Id(
val arg1: String,
val arg2: String
) {
constructor(raw: String) : this(raw.split("_")[0], raw.split("_")[1])
}
I can do this in Java but how I can do this in Kotlin?
You can do it this way:
data class Id(
val arg1: String,
val arg2: String
) {
private constructor(splitted: List<String>) : this(splitted[0], splitted[1])
constructor(raw: String) : this(raw.split("_"))
}
It's a good and idiomatic way to solve your problem. Since all secondary constructors must delegate to primary constructor (data class always has it), you can't do what you want in constructor body. In Java it works because there are no primary constructors and no data classes at language level - in Kotlin you can do it like in Java too if you remove data modifier and move properties outside of constructor, but it's a really bad way.

Kotlin data classes with Java super class

I have a Java class that holds generic information on databse entities (i.e. their id).
#Data
public class DbEntity {
protected final String id;
public DbEntity(String id) {
this.id = id;
}
}
We use Lombok #Data to generate getters, toString, equals...
In Java I would simply extend this class and add #Data once again.
#Data
class JavaSubClass extends DbEntity {
public JavaSubClass(String id) {
super(id);
}
}
In a newer service we use Kotlin but would like to reuse standard classes such as DbEntity.
My first approach was to simply declare a data class such as
data class SubClass1(val id: String, val name: String) : DbEntity(id)
Accidental override: The following declarations have the same JVM signature (getId()Ljava/lang/String;):
fun <get-id>(): String defined in com.demo.SubClass1
fun getId(): String! defined in com.demo.SubClass1
After some reading I found several solutions, all of which I'm not super happy with.
Don't use data classes. This works but leaves me with the task of implementing equals etc.
class SubClass4(id: String, val name: String) : DbEntity(id)
Duplicate the field. This works but we end up with two fields that could go out of sync.
data class SubClass3(val subId: String, val name: String) : DbEntity(subId)
Assign a different name to the getter. This fundamentally also duplicates the field, but hides the getter.
data class SubClass2(#get:JvmName("getId_") val id: String, val name: String) : DbEntity(id)
As I said, I'm not happy with any of the solution presented above. Having an abstract super class or an interface instead would certainly be more appropriate. However the Entity class resides in a library that primarily Java projects depend on. I'm hesitant to change it just because of a new Kotlin dependnecy.
Did anyone encounter similar issues and has advice on how to solve them?
As a workaround, until KT-6653 - Kotlin properties do not override Java-style getters and setters is fixed, I would go for a variant of your point 3, i.e.:
data class SubClass(#get:JvmName("bogusId") private val id: String, val name: String) : DbEntity(id)
The benefit of this variant is, that you always access the "original" getId-function. You will not use the bogusId()-function as it is not visible/accessible (accessing it via reflection makes no sense... you are only interested in the actual id-field). This works and looks similar for both sides: from Java as also from Kotlin. Still, under the hood this variant uses 2 fields, but in the best case you can just replace it in future with something like:
data class SubClass(override val id: String, val name : String) : DbEntity(id)

What does 'by' keyword do in Kotlin?

While developing for android I sometimes come across something that looks like this:
var someModel: someViewModel by notNullAndObservable { vm ->
...
}
I don't understand what the significance of the by keyword is.
In simple words, you can understand by keyword as provided by.
From the perspective of property consumer, val is something that has getter (get) and var is something that has getter and setter (get, set). For each var property there is a default provider of get and set methods that we don't need to specify explicitly.
But, when using by keyword, you are stating that this getter/getter&setter is provided elsewhere (i.e. it's been delegated). It's provided by the function that comes after by.
So, instead of using this built-in get and set methods, you are delegating that job to some explicit function.
One very common example is the by lazy for lazy loading properties.
Also, if you are using dependency injection library like Koin, you'll see many properties defined like this:
var myRepository: MyRepository by inject() //inject is a function from Koin
In the class definition, it follows the same principle, it defines where some function is provided, but it can refer to any set of methods/properties, not just get and set.
class MyClass: SomeInterface by SomeImplementation, SomeOtherInterface
This code is saying:
'I am class MyClass and I offer functions of interface SomeInterface which are provided by SomeImplementation.
I'll implement SomeOtherInterface by myself (that's implicit, so no by there).'
In the Kotlin reference you will find two uses for by, the first being Delegated Properties which is the use you have above:
There are certain common kinds of properties, that, though we can implement them manually every time we need them, would be very nice to implement once and for all, and put into a library. Examples include lazy properties: the value gets computed only upon first access,
observable properties: listeners get notified about changes to this property,
storing properties in a map, not in separate field each.
Here you delegate the getter/setter to another class that does the work and can contain common code. As another example, some of the dependency injectors for Kotlin support this model by delegating the getter to receiving a value from a registry of instances managed by the dependency injection engine.
And Interface/Class delegation is the other use:
The Delegation pattern has proven to be a good alternative to implementation inheritance, and Kotlin supports it natively requiring zero boilerplate code. A class Derived can inherit from an interface Base and delegate all of its public methods to a specified object
Here you can delegate an interface to another implementation so the implementing class only needs to override what it wants to change, while the rest of the methods delegate back to a fuller implementation.
A live example would be the Klutter Readonly/Immutable collections where they really just delegate the specific collection interface to another class and then override anything that needs to be different in the readonly implementation. Saving a lot of work not having to manually delegate all of the other methods.
Both of these are covered by the Kotlin language reference, start there for base topics of the language.
The syntax is:
val/var <property name>: <Type> by <expression>.
The expression after by is the delegate
if we try to access the value of property p, in other words, if we call get() method of property p, the getValue() method of Delegate instance is invoked.
If we try to set the value of property p, in other words, if we call set() method of property p, the setValue() method of Delegate instance is invoked.
Delegation for property:
import kotlin.reflect.KProperty
class Delegate {
// for get() method, ref - a reference to the object from
// which property is read. prop - property
operator fun getValue(ref: Any?, prop: KProperty<*>) = "textA"
// for set() method, 'v' stores the assigned value
operator fun setValue(ref: Any?, prop: KProperty<*>, v: String) {
println("value = $v")
}
}
object SampleBy {
var s: String by Delegate() // delegation for property
#JvmStatic fun main(args: Array<String>) {
println(s)
s = "textB"
}
}
Result:
textA
value = textB
Delegation for class:
interface BaseInterface {
val value: String
fun f()
}
class ClassA: BaseInterface {
override val value = "property from ClassA"
override fun f() { println("fun from ClassA") }
}
// The ClassB can implement the BaseInterface by delegating all public
// members from the ClassA.
class ClassB(classA: BaseInterface): BaseInterface by classA {}
object SampleBy {
#JvmStatic fun main(args: Array<String>) {
val classB = ClassB(ClassA())
println(classB.value)
classB.f()
}
}
Result:
property from ClassA
fun from ClassA
Delegation for parameters:
// for val properties Map is used; for var MutableMap is used
class User(mapA: Map<String, Any?>, mapB: MutableMap<String, Any?>) {
val name: String by mapA
val age: Int by mapA
var address: String by mapB
var id: Long by mapB
}
object SampleBy {
#JvmStatic fun main(args: Array<String>) {
val user = User(mapOf("name" to "John", "age" to 30),
mutableMapOf("address" to "city, street", "id" to 5000L))
println("name: ${user.name}; age: ${user.age}; " +
"address: ${user.address}; id: ${user.id}")
}
}
Result:
name: John; age: 30; address: city, street; id: 5000