Kotlin delegate all fields by class property - kotlin

I have a wrapper class Manager that has a property managerInfo of type UserInfo that I can’t modify. The wrapper class just add business rules around the info object. In code I need to access properties of the managerInfo and write each time manager.managerInfo.username is a little verbose.
I know that I can delegate property to this info object like that:
class Manager {
...
val username by UserInfo
...
}
And then I can simply do manager.username. But the problem is that the info class has about 15 properties and do this manually will be messy.
Is there is way to delegate all properties by means of Kotlin lang or by some library?

You can do implementation by delegation, which looks like this:
interface UserCommon {
val username: String
val email: String
}
data class UserInfo(
override var username: String,
override var email: String
) : UserCommon
class Transaction(
userInfo: UserInfo
) : UserCommon by userInfo
After that class Transaction will have all properties that UserCommon does, but implementation is delegated to userInfo which is passed to constructor.
The other way is to convert UserInfo to MutableMap and delegate property invocation to that map:
class Transaction(
userInfoMap: HashMap<String, Any>
) {
var username: String by userInfoMap
var email: String by userInfoMap
}

Related

Share common properties between Kotlin classes

I have 2 (data) classes that almost share the same properties:
data class Foo(
val id: FooId,
val name: String,
... 10+ properties
}
data class NewFoo(
val name: String,
... 10+ properties
}
I just want some syntax sugar magic here: not to repeat 10+ properties. I can make a base sealed class, but you would end up writing even more text (for passing arguments to base class ctor), although you are safer from making a mistake.
Yes, I know I could use composition for this, but here I don't want to, as there might be different 'variants' of the same data.
Am I missing something or this is not possible in Kotlin?
You can use an abstract (or sealed) class with abstract params instead and override them in the constructor of your data class (i.e. without additional passing them into the constructor of the base class).
abstract class Base {
// put commons parameter here
// abstract param needs to be initialized in the constructor of data class
abstract val name: String
// you can define some not-abstract params as well
open lateinit var someOtherParam: String
}
data class Foo1(
override val name: String,
val id: Int,
val someAdditionalParam1: String
) : Base()
data class Foo2(
override val name: String,
val someAdditionalParam2: String,
override var someOtherParam: String
) : Base()

Delegation to another object of same type using by does not even compile

I am trying to understand how the delegate keyword by works.
So delegating to implemenent an interface is clear e.g.
class Manager(clientele: List<Client> = ArrayList()): List<Client> by clientale
But the following does not work:
data class Client(val name: String, val postalCode: Int)
fun createClient() = Client("Bob", 1234)
val bigClient: Client by createClient() // compilation error
I get the error:
Missing getValue(Nothing?, KProperty<*>) method delegate of type
Client
I thought that if two objects are the same the delegation from one to the other (Client by Client) would work.
Can someone please explain what is the error here and what am I doing wrong?
Unfortunately that's not exactly how delegation of properties works. Based on the documentation:
For a read-only property (i.e. a val), a delegate has to provide a function named getValue that takes the following parameters:
thisRef - must be the same or a supertype of the property owner;
property - must be of type KProperty<*> or its supertype.
For a mutable property (a var), a delegate has to additionally provide a function named setValue that takes the following parameters:
thisRef - same as for getValue();
property - same as for getValue();
newValue - must be of the same type as the property or its subtype.
[...] Both of the functions need to be marked with the operator keyword.
So in order just to make your example work, you have to add a getValue() method which meets the above requirements:
data class Client(val name: String, val postalCode: Int) {
operator fun getValue(thisRef: Nothing?, property: KProperty<*>): Client = this
}
You can also use and implement the ReadOnlyProperty and ReadWriteProperty interfaces which provide the required methods:
data class Client(val name: String, val postalCode: Int) : ReadOnlyProperty<Nothing?, Client> {
override fun getValue(thisRef: Nothing?, property: KProperty<*>): Client = this
}
Edit:
What is this getValue() supposed to do?
Let me explain a little further on a more abstract example. We have the following classes:
class MyDelegate : ReadWriteProperty<MyClass, String> {
private var delegateProperty: String = ""
override fun getValue(thisRef: MyClass, property: KProperty<*>): String {
println("$thisRef delegated getting the ${property.name}'s value to $this")
return delegateProperty
}
override fun setValue(thisRef: MyClass, property: KProperty<*>, value: String) {
println("$thisRef delegated setting the ${property.name}'s value to $this, new value: $value")
delegateProperty = value
}
}
class MyClass {
var property: String by MyDelegate()
}
The above MyClass would get compiled more or less to:
class MyClass {
private var property$delegate: MyDelegate = MyDelegate()
var property: String
get() = property$delegate.getValue(this, this::property)
set(value) = property$delegate.setValue(this, this::property, value)
}
So you can see that the compiler requires a delegate to have getValue() and setValue() methods for mutable properties (var) or only getValue() for immutable properites (val), because it uses them to respectively get and set the delegated property's value.
What are Nothing and KProperty<*>?
KProperty<*> is a Kotlin class which represents a property and provides its metadata.
Nothing is a type that represents a value that doesn't exist. It's quite irrelevant from the delegation point of view. It came up in this case, because you probably defined the bigClient property outside any class so it has no owner, hence thisRef is Nothing.

POJO class mismatch

I have the following class User that extends the BaseResponse class. I
am getting a type mismatch error:
Required => String
Found => String.Companion
for return apiKey
package com.touchsides.rxjavanetworking.network.model
import com.google.gson.annotations.SerializedName
class User: BaseResponse()
{
#SerializedName("api_key")
val apiKey = String
fun getApiKey(): String
{
return apiKey
}
}
abstract class BaseResponse(var error: String?=null)
{
}
How is the current implementation of this wrong
You used = instead : while declaration of api_key (apiKey = String). Which actually means you are initialising api_key with String.Companion Object.
And you don't need to create getApiKey() (getter) method as by default you will have getter method for your properties.
class User : BaseResponse() {
#SerializedName("api_key")
var apiKey: String? = null
private set
}
abstract class BaseResponse(var error: String? = null)
in fact you can use data class for this purposes
data class User(#SerializedName("api_key") val apiKey: String):BaseResponse()
fun main(args: Array<String>) {
Gson().fromJson<User>("{\"api_key\":\"my api key\"}", User::class.java).let {
println(it.apiKey)
}
}
A complete answer is that your code should look like this:
class User: BaseResponse()
{
#SerializedName("api_key")
lateinit var apiKey: String // must be set by something before being read
}
abstract class BaseResponse(var error: String?=null) {
}
You do not need a default value for the apiKey property if you intend to set it via deserialization later, if not then you should also add a default value as below. The getApiKey() method is removed because you do not need that in Kotlin, all properties have automatically generated getters built-in and by adding your own you would end up with a conflict between the generated getter and the one you manually created (two methods with the same name, same signature).
If you do need a default value for apiKey then stay with a var so that deserialization can work (if you intend to do that) and add a default empty string or make it a nullable string and set it to null.
class User: BaseResponse()
{
#SerializedName("api_key")
var apiKey: String = "" // if you want a default regardless, or make it nullable and null
}
abstract class BaseResponse(var error: String?=null) {}
You're stuck with the way Java do things. In kotlin when defining Getter and Setter, you don't have to write them yourself. Once you declare a variable, both methods would be automatically created.
EDIT: So delete the getter in your POJO class.

Initialize val property on Kotlin Data Class via Secondary constructor

class Animal {
val name: String
constructor(name: String){
this.name = name // initialized via constructor
}
}
For the above class in Kotlin I am able to initialize a val property via secondary constructor but the same is not allowed for Data classes
data class User(val name: String, val postalCode: Int) {
val email: String
constructor( email: String): this("", 1){
this.email = email // error: value can not be reassigned
}
}
What I can't understand is, where is the email property is initialized already as I haven't declared any initializes?
If your class has a primary constructor, you have to initialize all of its properties "in the primary constructor" - either by directly initializing them at their declaration:
val email = "foo#bar.com"
Or in an initializer block:
val email: String
init {
email = "foo#bar.com"
}
The compiler forces you to forward all secondary constructor calls to the to the primary constructor, and since the primary constructor already has to initialize all properties inside the class (otherwise calling it would construct a partially initialized instance, like in your code example), it wouldn't make sense to also initialize them in the body of the secondary constructor, especially for a val which cannot be reassigned.

Data class constructor with two different constructor in Kotlin

I am new to Kotlin. I want to write a class which holds data. I want two constructor. What i want is something like this
class InstituteSearchDetails (var centerId: String) {
lateinit var centerId: String;
lateinit var instituteName: String;
lateinit var city: String;
init {
this.centerId=centerId
}
constructor( instituteName: String, city: String)
{
this.instituteName=instituteName;
this.city=city;
}
}
But on Secondary constructor line it says primary constructor call is required. I know some delegation is required which call primary constructor form there. I cant call primary constructor from here. I am sorry if i am doing some silly mistake. I am new to this thing
From the doc:
If the class has a primary constructor, each secondary constructor
needs to delegate to the primary constructor, either directly or
indirectly through another secondary constructor(s). Delegation to
another constructor of the same class is done using the this keyword:
Example:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
Your code:
constructor( instituteName: String, city: String) : this("centerId"){
this.instituteName=instituteName;
this.city=city;
}
But it doesn't look like you have the centerId value in the secondary constructor.
You can have two secondary constructors:
class InstituteSearchDetails {
lateinit var centerId: String;
lateinit var instituteName: String;
lateinit var city: String;
constructor(centerId: String) {
this.centerId = centerId
}
constructor( instituteName: String, city: String)
{
this.instituteName=instituteName;
this.city=city;
}
}
But be aware that, for instance, centerId wouldn't have been initialized if you use the second constructor and you will get an exception (UninitializedPropertyAccessException) if you try to access the centerId in that case.
Edit:
This is not possible in data class because data class requires a primary constructor with at least one val or var. If you have the primary constructor, then your secondary constructor should delegate to the primary constructor as well. Perhaps you can have all properties in a single primary constructor of a data class but with nullable properties. Or see Sealed class.
sealed class InstituteSearchDetails {
data class InstituteWithCenterId(val centerId: String): InstituteSearchDetails()
data class InstituteWithNameAndCity(val name: String, val city: String): InstituteSearchDetails()
}
fun handleInstitute(instituteSearchDetails: InstituteSearchDetails) {
when (instituteSearchDetails) {
is InstituteSearchDetails.InstituteWithCenterId -> println(instituteSearchDetails.centerId)
is InstituteSearchDetails.InstituteWithNameAndCity -> println(instituteSearchDetails.name)
}
}