SlideShare a Scribd company logo
Kotlin delegates
in practice
Fabio Collini
@fabioCollini
“”
“The ratio of time spent reading (code)

versus writing is well over 10 to 1

(therefore) making it easy to read

makes it easier to write

Robert C. Martin
“”
“The Principle of Least Astonishment states that

the result of performing some operation should be
obvious, consistent, and predictable, based upon
the name of the operation and other clues

https://wiki.c2.com/?PrincipleOfLeastAstonishment
Kotlin code is readable
Data classes
Extension functions
Sealed classes
Coroutines
Delegates
…
Delegates
public class MyEquivalentJavaClass {
private final String property = slowMethod();
public String getProperty() {
return property;
}5
}6
class MyClass {
val property = slowMethod()
}1
public class MyEquivalentJavaClass {
private String property = slowMethod();
public String getProperty() {
return property;
}
public void setProperty(String var1) {
this.property = var1;
}5
}6
class MyClass {
var property = slowMethod()
}1
public class MyEquivalentJavaClass {
private String property = slowMethod();
}6
class MyClass {
private var property = slowMethod()
}1
public class MyEquivalentJavaClass {
public String getProperty() {
return slowMethod();
}5
}6
class MyClass {
val property get() = slowMethod()
}1
class MyClass {
val property = slowMethod()
}1
fun main() {
val obj = MyClass()
println(obj.property)A
println(obj.property)B
}2
class MyClass {
val property = slowMethod()
}1
fun main() {
println("starting")
val obj = MyClass()
println("obj created")
println(obj.property)A
println(obj.property)B
println("end")
}2
starting
slowMethod invoked
obj created
end
class MyClass {
val property get() = slowMethod()
}1
fun main() {
println("starting")
val obj = MyClass()
println("obj created")
println(obj.property)A
println(obj.property)B
println("end")
}2
starting
obj created
slowMethod invoked
slowMethod invoked
end
class MyClass {
val property by lazy {
slowMethod()
}3
}1
fun main() {
println("starting")
val obj = MyClass()
println("obj created")
println(obj.property)A
println(obj.property)B
println("end")
}2
starting
obj created
slowMethod invoked
end
class MyClass {
val property by lazy {
slowMethod()
}3
}1
by
public class MyEquivalentJavaClass {
private SimpleLazy lazy = new SimpleLazy(::slowMethod);
public String getProperty() {
return lazy.getValue();
}
}
internal object UNINITIALIZED_VALUE
class SimpleLazy<T>(private val initializer: () -> T) {
private var value: Any? = UNINITIALIZED_VALUE
fun getValue(): T {
if (value == UNINITIALIZED_VALUE) {
value = initializer()
}4
return value as T
}3
}2
/**
* Specifies how a Lazy instance synchronizes initialization among multiple threads.
*/
public enum class LazyThreadSafetyMode {
/**
* Locks are used to ensure that only a single thread can initialize the Lazy instance.
*/
SYNCHRONIZED,
/**
* Initializer function can be called several times on concurrent access to
* uninitialized Lazy instance value,
* but only the first returned value will be used as the value of Lazy instance.
*/
PUBLICATION,
/**
* No locks are used to synchronize an access to the Lazy instance value;
* if the instance is accessed from multiple threads, its behavior is undefined.
*
* This mode should not be used unless the Lazy instance is guaranteed never to
* be initialized from more than one thread.
*/
NONE
}
Default value
class MyClass {
val notAGoodIdea by lazy {
2 + 2
}3
}1
class MyClass {
suspend val property by lazy {
slowMethod()
}1
}2
suspend fun slowMethod() = withContext(IO) {
//...
}3
Modifier 'suspend' is not applicable to 'member property with delegate'
Suspend function 'slowMethod' should be called only from a coroutine or another suspend function
Kotlin Delegates in practice - Kotlin community conf
class MyFragment : Fragment() {
private val appName by lazy { getString(R.string.app_name) }1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("Starting $appName...")
}2
}3
Lazy
class MyFragment : Fragment() {
private lateinit var appName: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appName = getString(R.string.app_name)
println("Starting $appName...")
}4
}5
lateinit
class MyFragment : Fragment() {
private lateinit var appName: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appName = getString(R.string.app_name)
println("Starting $appName...")
}4
}5
lateinit
class MyFragment : Fragment() {
private val appName by lazy { getString(R.string.app_name) }1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("Starting $appName...")
}2
}3
Lazy
class MyFragment : Fragment() {
private lateinit var appName: String
private lateinit var title: String
private lateinit var summary: String
private lateinit var text: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appName = getString(R.string.app_name)
title = getString(R.string.title)
summary = getString(R.string.summary)
text = "$appNamen$titlen$summary"
println(text)
}4
}5
lateinit class MyFragment2 : Fragment() {
private val appName by lazy { getString(R.string.app_name) }
private val title by lazy { getString(R.string.title) }
private val summary by lazy { getString(R.string.summary) }
private val text by lazy { "$appNamen$titlen$summary" }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println(text)
}2
}3
Lazy
Custom
delegates
operator fun getValue(thisRef: Any, property: KProperty<*>): T
/**
* Base interface that can be used for implementing property delegates of read-only properties.
*1
* This is provided only for convenience; you don't have to extend this interface
* as long as your property delegate has methods with the same signatures.
*2
* @param R the type of object which owns the delegated property.
* @param T the type of the property value.
*/
interface ReadOnlyProperty<in R, out T> {
/**
* Returns the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @return the property value.
*/
operator fun getValue(thisRef: R, property: KProperty<*>): T
}3
class LogDelegate<T>(initialValue: T) : ReadOnlyProperty<Any, T> {
private var value: T = initialValue
override fun getValue(thisRef: Any, property: KProperty<*>): T {
println("get invoked on $thisRef.${property.name}")
return value
}1
}2
class LogDelegate<T>(initialValue: T) : ReadOnlyProperty<Any, T> {
private var value: T = initialValue
override fun getValue(thisRef: Any, property: KProperty<*>): T {
println("get invoked on $thisRef.${property.name}")
return value
}1
}2
class MyClass {
val property by LogDelegate("ABC")
}3
class LogDelegate<T>(initialValue: T) : ReadOnlyProperty<Any, T> {
private var value: T = initialValue
override fun getValue(thisRef: Any, property: KProperty<*>): T {
println("get invoked on $thisRef.${property.name}")
return value
}1
}2
class MyClass {
val property by LogDelegate("ABC")
}3
fun main() {
val obj = MyClass()
println(obj.property)
}4
get invoked on MyClass@87aac27.property
ABC
/**
* Base interface that can be used for implementing property delegates of read-write properties.
*
* This is provided only for convenience; you don't have to extend this interface
* as long as your property delegate has methods with the same signatures.
*
* @param R the type of object which owns the delegated property.
* @param T the type of the property value.
*/
interface ReadWriteProperty<in R, T> {
/**
* Returns the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @return the property value.
*/
operator fun getValue(thisRef: R, property: KProperty<*>): T
/**
* Sets the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @param value the value to set.
*/
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
class LogDelegate<T>(initialValue: T) : ReadWriteProperty<Any, T> {
private var value: T = initialValue
override fun getValue(thisRef: Any, property: KProperty<*>): T {
println("get invoked on $thisRef.${property.name}")
return value
}1
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
println("set invoked on $thisRef.${property.name} with value $value")
this.value = value
}5
}2
class MyClass {
var property by LogDelegate("ABC")
}3
fun main() {
val obj = MyClass()
println(obj.property)
obj.property = "DEF"
println(obj.property)
}4
get invoked on MyClass@e9e54c2.property
ABC
set invoked on MyClass@e9e54c2.property with value DEF
get invoked on MyClass@e9e54c2.property
DEF
A real
example
class TokenHolder(private val prefs: SharedPreferences) {
val token: String?
get() = prefs.getString(TOKEN, null)
val count: Int
get() = prefs.getInt(COUNT, 0)
fun saveToken(newToken: String) {
prefs.edit {
putString(TOKEN, newToken)
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val TOKEN = "token"
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
val token: String?
get() = prefs.getString(TOKEN, null)
val count: Int
get() = prefs.getInt(COUNT, 0)
fun saveToken(newToken: String) {
prefs.edit {
putString(TOKEN, newToken)
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val TOKEN = "token"
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
val token: String?
get() = prefs.getString(TOKEN, null)
val count: Int
get() = prefs.getInt(COUNT, 0)
fun saveToken(newToken: String) {
prefs.edit {
putString(TOKEN, newToken)
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val TOKEN = "token"
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
val token: String?
get() = prefs.getString(TOKEN, null)
val count: Int
get() = prefs.getInt(COUNT, 0)
fun saveToken(newToken: String) {
prefs.edit {
putString(TOKEN, newToken)
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val TOKEN = "token"
private const val COUNT = "count"
}3
}4
fun main() {
val tokenHolder = TokenHolder(sharedPreferences())
tokenHolder.saveToken("ABC")
println("${tokenHolder.token} - ${tokenHolder.count}")
tokenHolder.saveToken("DEF")
println("${tokenHolder.token} - ${tokenHolder.count}")
}
ABC - 1
DEF - 2
fun SharedPreferences.int() =
object : ReadWriteProperty<Any, Int> {
override fun getValue(thisRef: Any, property: KProperty<*>) =
getInt(property.name, 0)
override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) =
edit { putInt(property.name, value) }
}1
fun SharedPreferences.int() =
object : ReadWriteProperty<Any, Int> {
override fun getValue(thisRef: Any, property: KProperty<*>) =
getInt(property.name, 0)
override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) =
edit { putInt(property.name, value) }
}1
fun SharedPreferences.int(key: String, defaultValue: Int = 0) =
object : ReadWriteProperty<Any, Int> {
override fun getValue(thisRef: Any, property: KProperty<*>) =
getInt(key, defaultValue)
override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) =
edit { putInt(key, value) }
}1
fun SharedPreferences.int(key: String, defaultValue: Int = 0) =
object : ReadWriteProperty<Any, Int> {
override fun getValue(thisRef: Any, property: KProperty<*>) =
getInt(key, defaultValue)
override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) =
edit { putInt(key, value) }
}1
fun SharedPreferences.string(key: String, defaultValue: String? = null) =
object : ReadWriteProperty<Any, String?> {
override fun getValue(thisRef: Any, property: KProperty<*>) =
getString(key, defaultValue)
override fun setValue(thisRef: Any, property: KProperty<*>, value: String?) =
edit { putString(key, value) }
}2
class TokenHolder(private val prefs: SharedPreferences) {
val token: String?
get() = prefs.getString(TOKEN,0null)1
val count: Int
get() = prefs.getInt(COUNT, 0)
fun saveToken(newToken: String) {
prefs.edit {
putString(TOKEN, newToken)
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val TOKEN = "token"
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
var token by prefs.string(TOKEN)1
private3set
val count: Int
get() = prefs.getInt(COUNT, 0)
fun saveToken(newToken: String) {
prefs.edit {
putString(TOKEN, newToken)
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val TOKEN = "token"
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
var token by prefs.string(TOKEN)1
private set
val count: Int
get() = prefs.getInt(COUNT, 0)
fun saveToken(newToken: String) {
token = newToken
prefs.edit {
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val TOKEN = "token"
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
var token by prefs.string("token")1
private set
val count: Int
get() = prefs.getInt(COUNT, 0)2
fun saveToken(newToken: String) {
token = newToken
prefs.edit {
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
var token by prefs.string("token")
private set
var count by prefs.int(COUNT)2
private set
fun saveToken(newToken: String) {
token = newToken
prefs.edit {
putInt(COUNT, count + 1)
}1
}2
companion object {
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
var token by prefs.string("token")
private set
var count by prefs.int(COUNT)2
private set
fun saveToken(newToken: String) {
token = newToken
count++
}2
companion object {
private const val COUNT = "count"
}3
}4
class TokenHolder(private val prefs: SharedPreferences) {
var token by prefs.string("token")
private set
var count by prefs.int("count")2
private set
fun saveToken(newToken: String) {
token = newToken
count++
}2
}4
class TokenHolder(prefs: SharedPreferences) {
var token by prefs.string("token")
private set
var count by prefs.int("count")
private set
fun saveToken(newToken: String) {
token = newToken
count++
}2
}4
prefs.edit {
putInt("count", prefs.getInt("count", 0) + 1)
}
class DemoFragment : Fragment() {
private val component by lazy {
//...
}
private val viewModel by viewModelProvider {
component.myViewModel()
}
//...
}
https://proandroiddev.com/kotlin-delegates-in-android-development-part-2-2c15c11ff438
class DemoFragment : Fragment() {
private var param1: Int by argument()
private var param2: String by argument()
companion object {
fun newInstance(param1: Int, param2: String): DemoFragment =
DemoFragment().apply {
this.param1 = param1
this.param2 = param2
}
}
}
https://proandroiddev.com/kotlin-delegates-in-android-1ab0a715762d
Standard
delegates
object AnalyticsLib {
fun trackEvent(event: Map<String, Any?>) {
println(event)
}1
}2
fun main() {
val event = mapOf(
"name" to "myEvent",
"value" to 123
)3
AnalyticsLib.trackEvent(event)
}4
fun main() {
val event = mapOf(
"name" to "myEvent",
"value" to 123
)3
AnalyticsLib.trackEvent(event)
}4
const val NAME = "name"
const val VALUE = "value"
fun main() {
val event = mapOf(
NAME to "myEvent",
VALUE to 123
)3
AnalyticsLib.trackEvent(event)
}4
const val NAME = "name"
const val VALUE = "value"
fun main() {
val event = mapOf(
NAME to "myEvent",
VALUE to "this should be an Int :("
)3
AnalyticsLib.trackEvent(event)
}4
class MyEvent {
val map: MutableMap<String, Any?> = mutableMapOf()
var name: String by map
var value: Int by map
}1
fun main() {
val event = MyEvent().apply {
name = "myEvent"
value = 123
}2
AnalyticsLib.trackEvent(event.map)
}3
data class MyEvent(val name: String, val value: Int) {
val map = mapOf(
"name" to name,
"value" to value
)
}1
fun main() {
val event = MyEvent(
name = "myEvent",
value = 123
)2
AnalyticsLib.trackEvent(event.map)
}3
object AbTestLib {
fun readValues() = mapOf<String, Any?>(
"featureEnabled" to true,
"delay" to 1000
)1
}2
object AbTestLib {
fun readValues() = mapOf<String, Any?>(
"featureEnabled" to true,
"delay" to 1000
)1
}2
fun main() {
val values = AbTestLib.readValues()
println(values["featureEnabled"] as Boolean)
println(values["delay"] as Int)
}3
true
1000
data class AbValues(private val map: Map<String, Any?>) {
val featureEnabled: Boolean by map
val delay: Int by map
}1
fun main() {
val values = AbValues(AbTestLib.readValues())
println(values.featureEnabled)
println(values.delay)
}2
data class AbValues(private val map: Map<String, Any?>) {
val featureEnabled: Boolean by map
val delay: Int by map
}1
fun main() {
val values = AbValues(
mapOf<String, Any?>(
"featureEnabled" to "true",
"delay" to "1000"
)
)
println(values.featureEnabled)
println(values.delay)
}2
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean
class User {
var name: String by Delegates.observable("<no name>") { prop, old, new ->
println("$old -> $new")
}
}
class User {
var name: String by Delegates.vetoable("<no name>") { prop, old, new ->
new.startsWith("f")
}
}
ObservableVetoable
class MyClass {
var myIntVar: Int by notNull()
fun onCreate() {
myIntVar = calculateValue()
}
}
notNull
Inheritance
Composition
Delegation
Inheritance
open class Class1 {
fun doSomething() = 123
}1
class Class2 : Class1()
Composition
class Class1 {
fun doSomething() = 123
}
class Class2 {
private val wrapped = Class1()
fun doSomething() = wrapped.doSomething()
}
Delegation
interface Interface1 {
fun doSomething(): Int
}4
class Class1 : Interface1 {
override fun doSomething() = 123
}5
class Class2(private val wrapped: Class1 = Class1())
: Interface1 by wrapped
Delegation
interface Interface1 {
fun doSomething(): Int
}4
class Class1 : Interface1 {
override fun doSomething() = 123
}5
class Class2 : Interface1 by Class1()
11
Delegation
interface Interface1 {
fun doSomething(): Int
}4
class Class1 : Interface1 {
override fun doSomething() = 123
}5
class Class2 : Interface1 by Class1()
fun main() {
val obj = Class2()
println(obj.doSomething())
}m
Composition
class Class1 {
fun doSomething() = 123
}2
class Class2 {
private val wrapped = Class1()
fun doSomething() =
wrapped.doSomething()
}3
Inheritance
open class Class1 {
fun doSomething() = 123
}1
class Class2 : Class1()
interface HasMargin {
val marginBottom: Int
val marginTop: Int
val marginLeft: Int
val marginRight: Int
}1
class Style(
val backgroundColor: Int,
override val marginBottom: Int,
override val marginTop: Int,
override val marginLeft: Int,
override val marginRight: Int
) : HasMargin
class View(style: Style) : HasMargin by style {
fun draw() {
//...
}
}
[
{
"type": "student",
"name": "studentName",
"surname": "studentSurname",
"age": 20,
"university": "universityName"
},
{
//...
}
]
data class PersonJson(
val type: String,
val name: String,
val surname: String,
val age: Int,
val university: String?,
val company: String?
)
abstract class Person(
val name: String,
val surname: String,
val age: Int
)
class Student(
name: String,
surname: String,
age: Int,
val university: String
) : Person(name, surname, age)
class Worker(
name: String,
surname: String,
age: Int,
val company: String
) : Person(name, surname, age)
fun main() {
val json: List<PersonJson> = listOf(/* ... */)
val people = json.map {
if (it.type == "student")
Student(
it.name,
it.surname,
it.age,
it.university!!
)
else
Worker(
it.name,
it.surname,
it.age,
it.company!!
)
}
println(people.joinToString { "${it.name} ${it.surname}" })
}
abstract class Person(
val_name: String,
val_surname: String,
val_age: Int
)
class Student(
name: String,
surname: String,
age: Int,
val university: String
) : Person(name, surname, age)_
class Worker(
name: String,
surname: String,
age: Int,
val company: String
) : Person(name, surname, age)_
abstract class Person {
abstract val_name: String
abstract val_surname: String
abstract val_age: Int
}1
data class Student(
override val name: String,
override val surname: String,
override val age: Int,
val university: String
) : Person()_
data class Worker(
override val name: String,
override val surname: String,
override val age: Int,
val company: String
) : Person()_
interface Person {
val name: String
val surname: String
val age: Int
}1
data class Student(
override val name: String,
override val surname: String,
override val age: Int,
val university: String
) : Person
data class Worker(
override val name: String,
override val surname: String,
override val age: Int,
val company: String
) : Person
interface Person {
val name: String
val surname: String
val age: Int
}1
interface Person {
val name: String
val surname: String
val age: Int
}1
data class PersonData(
override val name: String,
override val surname: String,
override val age: Int
) : Person
data class Student(
override val name: String,
override val surname: String,
override val age: Int,
val university: String
) : Person
data class Worker(
override val name: String,
override val surname: String,
override val age: Int,
val company: String
) : Person
data class Student(
override val name: String,
override val surname: String,
override val age: Int,
val university: String
) : Person
data class Worker(
override val name: String,
override val surname: String,
override val age: Int,
val company: String
) : Person
data class Student(
val data: PersonData,
val university: String
) : Person by data
data class Worker(
val data: PersonData,
val company: String
) : Person by data
fun main() {
val json: List<PersonJson> = listOf(/* ... */)
val people = json.map {
val data = PersonData(
it.name,
it.surname,
it.age
)
if (it.type == "student")
Student(data, it.university!!)
else
Worker(data, it.company!!)
}
println(people.joinToString { "${it.name} ${it.surname}" })
}
interface Person {
val name: String
val surname: String
val age: Int
}
data class Student(
override val name: String,
override val surname: String,
override val age: Int,
val university: String
) : Person
data class Worker(
override val name: String,
override val surname: String,
override val age: Int,
val company: String
) : Person
fun main() {
val json: List<PersonJson> = listOf(/* ... */)
val people = json.map {
if (it.type == "student")
Student(
it.name,
it.surname,
it.age,
it.university!!
)
else
Worker(
it.name,
it.surname,
it.age,
it.company!!
)
}
println(people.joinToString { "${it.name} ${it.surname}" })
}
interface Person {
val name: String
val surname: String
val age: Int
}
data class PersonData(
override val name: String,
override val surname: String,
override val age: Int
) : Person
data class Student(
val data: PersonData,
val university: String
) : Person by data
data class Worker(
val data: PersonData,
val company: String
) : Person by data
fun main() {
val json: List<PersonJson> = listOf(/* ... */)
val people = json.map {
val data = PersonData(
it.name,
it.surname,
it.age
)
if (it.type == "student")
Student(data, it.university!!)
else
Worker(data, it.company!!)
}
println(people.joinToString { "${it.name} ${it.surname}" })
}
DelegationInheritance
interface Person {
val name: String
val surname: String
val age: Int
val address: String
val city: String
val zipCode: String
val nation: String
val telephoneNumber1: String
val telephoneNumber2: String
val telephoneNumber3: String
}
data class Student(
override val name: String,
override val surname: String,
override val age: Int,
val university: String,
override val address: String,
override val city: String,
override val zipCode: String,
override val nation: String,
override val telephoneNumber1: String,
override val telephoneNumber2: String,
override val telephoneNumber3: String
) : Person
data class Worker(
override val name: String,
override val surname: String,
override val age: Int,
val company: String,
override val address: String,
override val city: String,
override val zipCode: String,
override val nation: String,
override val telephoneNumber1: String,
override val telephoneNumber2: String,
override val telephoneNumber3: String
) : Person
data class Unemployed(
override val name: String,
override val surname: String,
override val age: Int,
override val address: String,
override val city: String,
override val zipCode: String,
override val nation: String,
override val telephoneNumber1: String,
override val telephoneNumber2: String,
override val telephoneNumber3: String
) : Person
fun main() {
val json: List<PersonJson> = listOf(/* ... */)
val people = json.map {
if (it.type == "student")
Student(
it.name,
it.surname,
it.age,
it.university!!,
it.address,
it.city,
it.zipCode,
it.nation,
it.telephoneNumber1,
it.telephoneNumber2,
it.telephoneNumber3
)
else if (it.type == "worker")
Worker(
it.name,
it.surname,
it.age,
it.company!!,
it.address,
it.city,
it.zipCode,
it.nation,
it.telephoneNumber1,
it.telephoneNumber2,
it.telephoneNumber3
)
else
Unemployed(
it.name,
it.surname,
it.age,
it.address,
it.city,
it.zipCode,
it.nation,
it.telephoneNumber1,
it.telephoneNumber2,
it.telephoneNumber3
)
}
println(people.joinToString { "${it.name} ${it.surname}" })
}
interface Person {
val name: String
val surname: String
val age: Int
val address: String
val city: String
val zipCode: String
val nation: String
val telephoneNumber1: String
val telephoneNumber2: String
val telephoneNumber3: String
}
data class PersonData(
override val name: String,
override val surname: String,
override val age: Int,
override val address: String,
override val city: String,
override val zipCode: String,
override val nation: String,
override val telephoneNumber1: String,
override val telephoneNumber2: String,
override val telephoneNumber3: String
) : Person
data class Student(
val data: PersonData,
val university: String
) : Person by data
data class Worker(
val data: PersonData,
val company: String
) : Person by data
data class Unemployed(
val data: PersonData
) : Person by data
fun main() {
val json: List<PersonJson> = listOf(/* ... */)
val people = json.map {
val data = PersonData(
it.name,
it.surname,
it.age,
it.address,
it.city,
it.zipCode,
it.nation,
it.telephoneNumber1,
it.telephoneNumber2,
it.telephoneNumber3
)
if (it.type == "student")
Student(data, it.university!!)
else if (it.type == "worker")
Worker(data, it.company!!)
else
Unemployed(data)
}
println(people.joinToString { "${it.name} ${it.surname}" })
}
DelegationInheritance
Wrappingup
“”
“The ratio of time spent reading (code)

versus writing is well over 10 to 1

(therefore) making it easy to read

makes it easier to write

Robert C. Martin
Wrappingup
“”
“The Principle of Least Astonishment states that

the result of performing some operation should be
obvious, consistent, and predictable, based upon
the name of the operation and other clues

https://wiki.c2.com/?PrincipleOfLeastAstonishment
Wrappingup
Delegates can be useful to simplify code
but there are pros and cons!
Links&contacts
Simpler Kotlin class hierarchies using class delegation
proandroiddev.com/simpler-kotlin-class-hierarchies-using-class-delegation-35464106fed5
Kotlin delegates in Android development — Part 1
medium.com/hackernoon/kotlin-delegates-in-android-development-part-1-50346cf4aed7
Kotlin delegates in Android development — Part 2
proandroiddev.com/kotlin-delegates-in-android-development-part-2-2c15c11ff438
@fabioCollini
linkedin.com/in/fabiocollini
github.com/fabioCollini
medium.com/@fabioCollini
THANKS
FOR YOUR
ATTENTION
QUESTIONS?
@fabioCollini

More Related Content

PDF
Kotlin delegates in practice - Kotlin Everywhere Stockholm
PDF
Architectures in the compose world
PDF
Managing parallelism using coroutines
PDF
Using hilt in a modularized project
PDF
Workshop 26: React Native - The Native Side
PDF
Workshop 23: ReactJS, React & Redux testing
PDF
Practical Protocol-Oriented-Programming
PDF
Advanced javascript
Kotlin delegates in practice - Kotlin Everywhere Stockholm
Architectures in the compose world
Managing parallelism using coroutines
Using hilt in a modularized project
Workshop 26: React Native - The Native Side
Workshop 23: ReactJS, React & Redux testing
Practical Protocol-Oriented-Programming
Advanced javascript

What's hot (20)

PDF
Protocol-Oriented MVVM (extended edition)
PDF
Swift Delhi: Practical POP
PDF
Anonymous functions in JavaScript
PDF
Intro to Retrofit 2 and RxJava2
PDF
Practical Protocols with Associated Types
PPTX
Intro to Javascript
PDF
Workshop 25: React Native - Components
PDF
Protocol-Oriented MVVM
PDF
Redux Sagas - React Alicante
ODP
From object oriented to functional domain modeling
PPTX
Workshop 1: Good practices in JavaScript
PDF
Daggerate your code - Write your own annotation processor
PDF
Intro to JavaScript
PDF
Taming Core Data by Arek Holko, Macoscope
PDF
Workshop 5: JavaScript testing
PPTX
Scientific calcultor-Java
PPTX
Java final project of scientific calcultor
PDF
Reactive Programming with JavaScript
PPTX
Typescript barcelona
PDF
Workshop 20: ReactJS Part II Flux Pattern & Redux
Protocol-Oriented MVVM (extended edition)
Swift Delhi: Practical POP
Anonymous functions in JavaScript
Intro to Retrofit 2 and RxJava2
Practical Protocols with Associated Types
Intro to Javascript
Workshop 25: React Native - Components
Protocol-Oriented MVVM
Redux Sagas - React Alicante
From object oriented to functional domain modeling
Workshop 1: Good practices in JavaScript
Daggerate your code - Write your own annotation processor
Intro to JavaScript
Taming Core Data by Arek Holko, Macoscope
Workshop 5: JavaScript testing
Scientific calcultor-Java
Java final project of scientific calcultor
Reactive Programming with JavaScript
Typescript barcelona
Workshop 20: ReactJS Part II Flux Pattern & Redux
Ad

Similar to Kotlin Delegates in practice - Kotlin community conf (20)

PDF
Scala cheatsheet
PPTX
Oop lect3.pptx
PDF
Useful and Practical Functionalities in Realm
PPTX
RTTI and Namespaces.pptx ppt of c++ programming language
PPTX
K is for Kotlin
PDF
Introduction to Swift
PDF
Kotlin for Android Developers - 3
PDF
Why Spring <3 Kotlin
PPTX
Ian 20150116 java script oop
DOCX
EmptyCollectionException-java -- - Represents the situation in which.docx
PDF
1. Suppose you want to implement an ADT in which you can insert valu.pdf
PPT
Pxb For Yapc2008
PDF
Dependency Injection
ODP
AST Transformations at JFokus
PPTX
PDF
For each task, submit your source java code file.(1) Objective Im.pdf
PDF
#살아있다 #자프링외길12년차 #코프링2개월생존기
PDF
In this lab, we will write an application to store a deck of cards i.pdf
PDF
create-netflix-clone-02-server.pdf
Scala cheatsheet
Oop lect3.pptx
Useful and Practical Functionalities in Realm
RTTI and Namespaces.pptx ppt of c++ programming language
K is for Kotlin
Introduction to Swift
Kotlin for Android Developers - 3
Why Spring <3 Kotlin
Ian 20150116 java script oop
EmptyCollectionException-java -- - Represents the situation in which.docx
1. Suppose you want to implement an ADT in which you can insert valu.pdf
Pxb For Yapc2008
Dependency Injection
AST Transformations at JFokus
For each task, submit your source java code file.(1) Objective Im.pdf
#살아있다 #자프링외길12년차 #코프링2개월생존기
In this lab, we will write an application to store a deck of cards i.pdf
create-netflix-clone-02-server.pdf
Ad

More from Fabio Collini (20)

PDF
Using Dagger in a Clean Architecture project
PDF
Solid principles in practice the clean architecture - Droidcon Italy
PDF
SOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
PDF
SOLID principles in practice: the Clean Architecture
PDF
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
PDF
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
PDF
Recap Google I/O 2018
PDF
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
PDF
From java to kotlin beyond alt+shift+cmd+k
PDF
Testing Android apps based on Dagger and RxJava Droidcon UK
PDF
Testing Android apps based on Dagger and RxJava
PDF
Android Data Binding in action using MVVM pattern - droidconUK
PDF
Data Binding in Action using MVVM pattern
PDF
Android Wear CodeLab - GDG Firenze
PDF
Testable Android Apps using data binding and MVVM
PDF
Introduction to Retrofit and RxJava
PDF
Testable Android Apps DroidCon Italy 2015
PDF
Clean android code - Droidcon Italiy 2014
KEY
Librerie su Android: come non reinventare la ruota @ whymca 2012
KEY
Android Widget @ whymca 2011
Using Dagger in a Clean Architecture project
Solid principles in practice the clean architecture - Droidcon Italy
SOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
SOLID principles in practice: the Clean Architecture
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Recap Google I/O 2018
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k
Testing Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava
Android Data Binding in action using MVVM pattern - droidconUK
Data Binding in Action using MVVM pattern
Android Wear CodeLab - GDG Firenze
Testable Android Apps using data binding and MVVM
Introduction to Retrofit and RxJava
Testable Android Apps DroidCon Italy 2015
Clean android code - Droidcon Italiy 2014
Librerie su Android: come non reinventare la ruota @ whymca 2012
Android Widget @ whymca 2011

Recently uploaded (20)

PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PDF
How to Choose the Right IT Partner for Your Business in Malaysia
PDF
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
PPTX
Introduction to Artificial Intelligence
PDF
PTS Company Brochure 2025 (1).pdf.......
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PPTX
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
PDF
Navsoft: AI-Powered Business Solutions & Custom Software Development
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
PDF
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
PDF
Odoo Companies in India – Driving Business Transformation.pdf
PDF
AI in Product Development-omnex systems
PPTX
Essential Infomation Tech presentation.pptx
PDF
wealthsignaloriginal-com-DS-text-... (1).pdf
PDF
EN-Survey-Report-SAP-LeanIX-EA-Insights-2025.pdf
PDF
System and Network Administration Chapter 2
PDF
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
PPTX
L1 - Introduction to python Backend.pptx
PPTX
Operating system designcfffgfgggggggvggggggggg
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
Design an Analysis of Algorithms II-SECS-1021-03
How to Choose the Right IT Partner for Your Business in Malaysia
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
Introduction to Artificial Intelligence
PTS Company Brochure 2025 (1).pdf.......
Upgrade and Innovation Strategies for SAP ERP Customers
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
Navsoft: AI-Powered Business Solutions & Custom Software Development
How to Migrate SBCGlobal Email to Yahoo Easily
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
Odoo Companies in India – Driving Business Transformation.pdf
AI in Product Development-omnex systems
Essential Infomation Tech presentation.pptx
wealthsignaloriginal-com-DS-text-... (1).pdf
EN-Survey-Report-SAP-LeanIX-EA-Insights-2025.pdf
System and Network Administration Chapter 2
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
L1 - Introduction to python Backend.pptx
Operating system designcfffgfgggggggvggggggggg

Kotlin Delegates in practice - Kotlin community conf

  • 1. Kotlin delegates in practice Fabio Collini @fabioCollini
  • 2. “” “The ratio of time spent reading (code) versus writing is well over 10 to 1 (therefore) making it easy to read makes it easier to write Robert C. Martin
  • 3. “” “The Principle of Least Astonishment states that the result of performing some operation should be obvious, consistent, and predictable, based upon the name of the operation and other clues https://wiki.c2.com/?PrincipleOfLeastAstonishment
  • 4. Kotlin code is readable Data classes Extension functions Sealed classes Coroutines Delegates …
  • 6. public class MyEquivalentJavaClass { private final String property = slowMethod(); public String getProperty() { return property; }5 }6 class MyClass { val property = slowMethod() }1
  • 7. public class MyEquivalentJavaClass { private String property = slowMethod(); public String getProperty() { return property; } public void setProperty(String var1) { this.property = var1; }5 }6 class MyClass { var property = slowMethod() }1
  • 8. public class MyEquivalentJavaClass { private String property = slowMethod(); }6 class MyClass { private var property = slowMethod() }1
  • 9. public class MyEquivalentJavaClass { public String getProperty() { return slowMethod(); }5 }6 class MyClass { val property get() = slowMethod() }1
  • 10. class MyClass { val property = slowMethod() }1 fun main() { val obj = MyClass() println(obj.property)A println(obj.property)B }2
  • 11. class MyClass { val property = slowMethod() }1 fun main() { println("starting") val obj = MyClass() println("obj created") println(obj.property)A println(obj.property)B println("end") }2 starting slowMethod invoked obj created end
  • 12. class MyClass { val property get() = slowMethod() }1 fun main() { println("starting") val obj = MyClass() println("obj created") println(obj.property)A println(obj.property)B println("end") }2 starting obj created slowMethod invoked slowMethod invoked end
  • 13. class MyClass { val property by lazy { slowMethod() }3 }1 fun main() { println("starting") val obj = MyClass() println("obj created") println(obj.property)A println(obj.property)B println("end") }2 starting obj created slowMethod invoked end
  • 14. class MyClass { val property by lazy { slowMethod() }3 }1
  • 15. by
  • 16. public class MyEquivalentJavaClass { private SimpleLazy lazy = new SimpleLazy(::slowMethod); public String getProperty() { return lazy.getValue(); } }
  • 17. internal object UNINITIALIZED_VALUE class SimpleLazy<T>(private val initializer: () -> T) { private var value: Any? = UNINITIALIZED_VALUE fun getValue(): T { if (value == UNINITIALIZED_VALUE) { value = initializer() }4 return value as T }3 }2
  • 18. /** * Specifies how a Lazy instance synchronizes initialization among multiple threads. */ public enum class LazyThreadSafetyMode { /** * Locks are used to ensure that only a single thread can initialize the Lazy instance. */ SYNCHRONIZED, /** * Initializer function can be called several times on concurrent access to * uninitialized Lazy instance value, * but only the first returned value will be used as the value of Lazy instance. */ PUBLICATION, /** * No locks are used to synchronize an access to the Lazy instance value; * if the instance is accessed from multiple threads, its behavior is undefined. * * This mode should not be used unless the Lazy instance is guaranteed never to * be initialized from more than one thread. */ NONE } Default value
  • 19. class MyClass { val notAGoodIdea by lazy { 2 + 2 }3 }1
  • 20. class MyClass { suspend val property by lazy { slowMethod() }1 }2 suspend fun slowMethod() = withContext(IO) { //... }3 Modifier 'suspend' is not applicable to 'member property with delegate' Suspend function 'slowMethod' should be called only from a coroutine or another suspend function
  • 22. class MyFragment : Fragment() { private val appName by lazy { getString(R.string.app_name) }1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) println("Starting $appName...") }2 }3 Lazy
  • 23. class MyFragment : Fragment() { private lateinit var appName: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) appName = getString(R.string.app_name) println("Starting $appName...") }4 }5 lateinit
  • 24. class MyFragment : Fragment() { private lateinit var appName: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) appName = getString(R.string.app_name) println("Starting $appName...") }4 }5 lateinit class MyFragment : Fragment() { private val appName by lazy { getString(R.string.app_name) }1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) println("Starting $appName...") }2 }3 Lazy
  • 25. class MyFragment : Fragment() { private lateinit var appName: String private lateinit var title: String private lateinit var summary: String private lateinit var text: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) appName = getString(R.string.app_name) title = getString(R.string.title) summary = getString(R.string.summary) text = "$appNamen$titlen$summary" println(text) }4 }5 lateinit class MyFragment2 : Fragment() { private val appName by lazy { getString(R.string.app_name) } private val title by lazy { getString(R.string.title) } private val summary by lazy { getString(R.string.summary) } private val text by lazy { "$appNamen$titlen$summary" } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) println(text) }2 }3 Lazy
  • 27. operator fun getValue(thisRef: Any, property: KProperty<*>): T
  • 28. /** * Base interface that can be used for implementing property delegates of read-only properties. *1 * This is provided only for convenience; you don't have to extend this interface * as long as your property delegate has methods with the same signatures. *2 * @param R the type of object which owns the delegated property. * @param T the type of the property value. */ interface ReadOnlyProperty<in R, out T> { /** * Returns the value of the property for the given object. * @param thisRef the object for which the value is requested. * @param property the metadata for the property. * @return the property value. */ operator fun getValue(thisRef: R, property: KProperty<*>): T }3
  • 29. class LogDelegate<T>(initialValue: T) : ReadOnlyProperty<Any, T> { private var value: T = initialValue override fun getValue(thisRef: Any, property: KProperty<*>): T { println("get invoked on $thisRef.${property.name}") return value }1 }2
  • 30. class LogDelegate<T>(initialValue: T) : ReadOnlyProperty<Any, T> { private var value: T = initialValue override fun getValue(thisRef: Any, property: KProperty<*>): T { println("get invoked on $thisRef.${property.name}") return value }1 }2 class MyClass { val property by LogDelegate("ABC") }3
  • 31. class LogDelegate<T>(initialValue: T) : ReadOnlyProperty<Any, T> { private var value: T = initialValue override fun getValue(thisRef: Any, property: KProperty<*>): T { println("get invoked on $thisRef.${property.name}") return value }1 }2 class MyClass { val property by LogDelegate("ABC") }3 fun main() { val obj = MyClass() println(obj.property) }4 get invoked on MyClass@87aac27.property ABC
  • 32. /** * Base interface that can be used for implementing property delegates of read-write properties. * * This is provided only for convenience; you don't have to extend this interface * as long as your property delegate has methods with the same signatures. * * @param R the type of object which owns the delegated property. * @param T the type of the property value. */ interface ReadWriteProperty<in R, T> { /** * Returns the value of the property for the given object. * @param thisRef the object for which the value is requested. * @param property the metadata for the property. * @return the property value. */ operator fun getValue(thisRef: R, property: KProperty<*>): T /** * Sets the value of the property for the given object. * @param thisRef the object for which the value is requested. * @param property the metadata for the property. * @param value the value to set. */ operator fun setValue(thisRef: R, property: KProperty<*>, value: T) }
  • 33. class LogDelegate<T>(initialValue: T) : ReadWriteProperty<Any, T> { private var value: T = initialValue override fun getValue(thisRef: Any, property: KProperty<*>): T { println("get invoked on $thisRef.${property.name}") return value }1 override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { println("set invoked on $thisRef.${property.name} with value $value") this.value = value }5 }2 class MyClass { var property by LogDelegate("ABC") }3 fun main() { val obj = MyClass() println(obj.property) obj.property = "DEF" println(obj.property) }4 get invoked on MyClass@e9e54c2.property ABC set invoked on MyClass@e9e54c2.property with value DEF get invoked on MyClass@e9e54c2.property DEF
  • 35. class TokenHolder(private val prefs: SharedPreferences) { val token: String? get() = prefs.getString(TOKEN, null) val count: Int get() = prefs.getInt(COUNT, 0) fun saveToken(newToken: String) { prefs.edit { putString(TOKEN, newToken) putInt(COUNT, count + 1) }1 }2 companion object { private const val TOKEN = "token" private const val COUNT = "count" }3 }4
  • 36. class TokenHolder(private val prefs: SharedPreferences) { val token: String? get() = prefs.getString(TOKEN, null) val count: Int get() = prefs.getInt(COUNT, 0) fun saveToken(newToken: String) { prefs.edit { putString(TOKEN, newToken) putInt(COUNT, count + 1) }1 }2 companion object { private const val TOKEN = "token" private const val COUNT = "count" }3 }4
  • 37. class TokenHolder(private val prefs: SharedPreferences) { val token: String? get() = prefs.getString(TOKEN, null) val count: Int get() = prefs.getInt(COUNT, 0) fun saveToken(newToken: String) { prefs.edit { putString(TOKEN, newToken) putInt(COUNT, count + 1) }1 }2 companion object { private const val TOKEN = "token" private const val COUNT = "count" }3 }4
  • 38. class TokenHolder(private val prefs: SharedPreferences) { val token: String? get() = prefs.getString(TOKEN, null) val count: Int get() = prefs.getInt(COUNT, 0) fun saveToken(newToken: String) { prefs.edit { putString(TOKEN, newToken) putInt(COUNT, count + 1) }1 }2 companion object { private const val TOKEN = "token" private const val COUNT = "count" }3 }4
  • 39. fun main() { val tokenHolder = TokenHolder(sharedPreferences()) tokenHolder.saveToken("ABC") println("${tokenHolder.token} - ${tokenHolder.count}") tokenHolder.saveToken("DEF") println("${tokenHolder.token} - ${tokenHolder.count}") } ABC - 1 DEF - 2
  • 40. fun SharedPreferences.int() = object : ReadWriteProperty<Any, Int> { override fun getValue(thisRef: Any, property: KProperty<*>) = getInt(property.name, 0) override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) = edit { putInt(property.name, value) } }1
  • 41. fun SharedPreferences.int() = object : ReadWriteProperty<Any, Int> { override fun getValue(thisRef: Any, property: KProperty<*>) = getInt(property.name, 0) override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) = edit { putInt(property.name, value) } }1
  • 42. fun SharedPreferences.int(key: String, defaultValue: Int = 0) = object : ReadWriteProperty<Any, Int> { override fun getValue(thisRef: Any, property: KProperty<*>) = getInt(key, defaultValue) override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) = edit { putInt(key, value) } }1
  • 43. fun SharedPreferences.int(key: String, defaultValue: Int = 0) = object : ReadWriteProperty<Any, Int> { override fun getValue(thisRef: Any, property: KProperty<*>) = getInt(key, defaultValue) override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) = edit { putInt(key, value) } }1 fun SharedPreferences.string(key: String, defaultValue: String? = null) = object : ReadWriteProperty<Any, String?> { override fun getValue(thisRef: Any, property: KProperty<*>) = getString(key, defaultValue) override fun setValue(thisRef: Any, property: KProperty<*>, value: String?) = edit { putString(key, value) } }2
  • 44. class TokenHolder(private val prefs: SharedPreferences) { val token: String? get() = prefs.getString(TOKEN,0null)1 val count: Int get() = prefs.getInt(COUNT, 0) fun saveToken(newToken: String) { prefs.edit { putString(TOKEN, newToken) putInt(COUNT, count + 1) }1 }2 companion object { private const val TOKEN = "token" private const val COUNT = "count" }3 }4
  • 45. class TokenHolder(private val prefs: SharedPreferences) { var token by prefs.string(TOKEN)1 private3set val count: Int get() = prefs.getInt(COUNT, 0) fun saveToken(newToken: String) { prefs.edit { putString(TOKEN, newToken) putInt(COUNT, count + 1) }1 }2 companion object { private const val TOKEN = "token" private const val COUNT = "count" }3 }4
  • 46. class TokenHolder(private val prefs: SharedPreferences) { var token by prefs.string(TOKEN)1 private set val count: Int get() = prefs.getInt(COUNT, 0) fun saveToken(newToken: String) { token = newToken prefs.edit { putInt(COUNT, count + 1) }1 }2 companion object { private const val TOKEN = "token" private const val COUNT = "count" }3 }4
  • 47. class TokenHolder(private val prefs: SharedPreferences) { var token by prefs.string("token")1 private set val count: Int get() = prefs.getInt(COUNT, 0)2 fun saveToken(newToken: String) { token = newToken prefs.edit { putInt(COUNT, count + 1) }1 }2 companion object { private const val COUNT = "count" }3 }4
  • 48. class TokenHolder(private val prefs: SharedPreferences) { var token by prefs.string("token") private set var count by prefs.int(COUNT)2 private set fun saveToken(newToken: String) { token = newToken prefs.edit { putInt(COUNT, count + 1) }1 }2 companion object { private const val COUNT = "count" }3 }4
  • 49. class TokenHolder(private val prefs: SharedPreferences) { var token by prefs.string("token") private set var count by prefs.int(COUNT)2 private set fun saveToken(newToken: String) { token = newToken count++ }2 companion object { private const val COUNT = "count" }3 }4
  • 50. class TokenHolder(private val prefs: SharedPreferences) { var token by prefs.string("token") private set var count by prefs.int("count")2 private set fun saveToken(newToken: String) { token = newToken count++ }2 }4
  • 51. class TokenHolder(prefs: SharedPreferences) { var token by prefs.string("token") private set var count by prefs.int("count") private set fun saveToken(newToken: String) { token = newToken count++ }2 }4 prefs.edit { putInt("count", prefs.getInt("count", 0) + 1) }
  • 52. class DemoFragment : Fragment() { private val component by lazy { //... } private val viewModel by viewModelProvider { component.myViewModel() } //... } https://proandroiddev.com/kotlin-delegates-in-android-development-part-2-2c15c11ff438
  • 53. class DemoFragment : Fragment() { private var param1: Int by argument() private var param2: String by argument() companion object { fun newInstance(param1: Int, param2: String): DemoFragment = DemoFragment().apply { this.param1 = param1 this.param2 = param2 } } } https://proandroiddev.com/kotlin-delegates-in-android-1ab0a715762d
  • 55. object AnalyticsLib { fun trackEvent(event: Map<String, Any?>) { println(event) }1 }2 fun main() { val event = mapOf( "name" to "myEvent", "value" to 123 )3 AnalyticsLib.trackEvent(event) }4
  • 56. fun main() { val event = mapOf( "name" to "myEvent", "value" to 123 )3 AnalyticsLib.trackEvent(event) }4
  • 57. const val NAME = "name" const val VALUE = "value" fun main() { val event = mapOf( NAME to "myEvent", VALUE to 123 )3 AnalyticsLib.trackEvent(event) }4
  • 58. const val NAME = "name" const val VALUE = "value" fun main() { val event = mapOf( NAME to "myEvent", VALUE to "this should be an Int :(" )3 AnalyticsLib.trackEvent(event) }4
  • 59. class MyEvent { val map: MutableMap<String, Any?> = mutableMapOf() var name: String by map var value: Int by map }1 fun main() { val event = MyEvent().apply { name = "myEvent" value = 123 }2 AnalyticsLib.trackEvent(event.map) }3
  • 60. data class MyEvent(val name: String, val value: Int) { val map = mapOf( "name" to name, "value" to value ) }1 fun main() { val event = MyEvent( name = "myEvent", value = 123 )2 AnalyticsLib.trackEvent(event.map) }3
  • 61. object AbTestLib { fun readValues() = mapOf<String, Any?>( "featureEnabled" to true, "delay" to 1000 )1 }2
  • 62. object AbTestLib { fun readValues() = mapOf<String, Any?>( "featureEnabled" to true, "delay" to 1000 )1 }2 fun main() { val values = AbTestLib.readValues() println(values["featureEnabled"] as Boolean) println(values["delay"] as Int) }3 true 1000
  • 63. data class AbValues(private val map: Map<String, Any?>) { val featureEnabled: Boolean by map val delay: Int by map }1 fun main() { val values = AbValues(AbTestLib.readValues()) println(values.featureEnabled) println(values.delay) }2
  • 64. data class AbValues(private val map: Map<String, Any?>) { val featureEnabled: Boolean by map val delay: Int by map }1 fun main() { val values = AbValues( mapOf<String, Any?>( "featureEnabled" to "true", "delay" to "1000" ) ) println(values.featureEnabled) println(values.delay) }2 Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean
  • 65. class User { var name: String by Delegates.observable("<no name>") { prop, old, new -> println("$old -> $new") } } class User { var name: String by Delegates.vetoable("<no name>") { prop, old, new -> new.startsWith("f") } } ObservableVetoable
  • 66. class MyClass { var myIntVar: Int by notNull() fun onCreate() { myIntVar = calculateValue() } } notNull
  • 68. Inheritance open class Class1 { fun doSomething() = 123 }1 class Class2 : Class1()
  • 69. Composition class Class1 { fun doSomething() = 123 } class Class2 { private val wrapped = Class1() fun doSomething() = wrapped.doSomething() }
  • 70. Delegation interface Interface1 { fun doSomething(): Int }4 class Class1 : Interface1 { override fun doSomething() = 123 }5 class Class2(private val wrapped: Class1 = Class1()) : Interface1 by wrapped
  • 71. Delegation interface Interface1 { fun doSomething(): Int }4 class Class1 : Interface1 { override fun doSomething() = 123 }5 class Class2 : Interface1 by Class1() 11
  • 72. Delegation interface Interface1 { fun doSomething(): Int }4 class Class1 : Interface1 { override fun doSomething() = 123 }5 class Class2 : Interface1 by Class1() fun main() { val obj = Class2() println(obj.doSomething()) }m Composition class Class1 { fun doSomething() = 123 }2 class Class2 { private val wrapped = Class1() fun doSomething() = wrapped.doSomething() }3 Inheritance open class Class1 { fun doSomething() = 123 }1 class Class2 : Class1()
  • 73. interface HasMargin { val marginBottom: Int val marginTop: Int val marginLeft: Int val marginRight: Int }1 class Style( val backgroundColor: Int, override val marginBottom: Int, override val marginTop: Int, override val marginLeft: Int, override val marginRight: Int ) : HasMargin class View(style: Style) : HasMargin by style { fun draw() { //... } }
  • 74. [ { "type": "student", "name": "studentName", "surname": "studentSurname", "age": 20, "university": "universityName" }, { //... } ] data class PersonJson( val type: String, val name: String, val surname: String, val age: Int, val university: String?, val company: String? )
  • 75. abstract class Person( val name: String, val surname: String, val age: Int ) class Student( name: String, surname: String, age: Int, val university: String ) : Person(name, surname, age) class Worker( name: String, surname: String, age: Int, val company: String ) : Person(name, surname, age)
  • 76. fun main() { val json: List<PersonJson> = listOf(/* ... */) val people = json.map { if (it.type == "student") Student( it.name, it.surname, it.age, it.university!! ) else Worker( it.name, it.surname, it.age, it.company!! ) } println(people.joinToString { "${it.name} ${it.surname}" }) }
  • 77. abstract class Person( val_name: String, val_surname: String, val_age: Int ) class Student( name: String, surname: String, age: Int, val university: String ) : Person(name, surname, age)_ class Worker( name: String, surname: String, age: Int, val company: String ) : Person(name, surname, age)_
  • 78. abstract class Person { abstract val_name: String abstract val_surname: String abstract val_age: Int }1 data class Student( override val name: String, override val surname: String, override val age: Int, val university: String ) : Person()_ data class Worker( override val name: String, override val surname: String, override val age: Int, val company: String ) : Person()_
  • 79. interface Person { val name: String val surname: String val age: Int }1 data class Student( override val name: String, override val surname: String, override val age: Int, val university: String ) : Person data class Worker( override val name: String, override val surname: String, override val age: Int, val company: String ) : Person
  • 80. interface Person { val name: String val surname: String val age: Int }1
  • 81. interface Person { val name: String val surname: String val age: Int }1 data class PersonData( override val name: String, override val surname: String, override val age: Int ) : Person
  • 82. data class Student( override val name: String, override val surname: String, override val age: Int, val university: String ) : Person data class Worker( override val name: String, override val surname: String, override val age: Int, val company: String ) : Person
  • 83. data class Student( override val name: String, override val surname: String, override val age: Int, val university: String ) : Person data class Worker( override val name: String, override val surname: String, override val age: Int, val company: String ) : Person data class Student( val data: PersonData, val university: String ) : Person by data data class Worker( val data: PersonData, val company: String ) : Person by data
  • 84. fun main() { val json: List<PersonJson> = listOf(/* ... */) val people = json.map { val data = PersonData( it.name, it.surname, it.age ) if (it.type == "student") Student(data, it.university!!) else Worker(data, it.company!!) } println(people.joinToString { "${it.name} ${it.surname}" }) }
  • 85. interface Person { val name: String val surname: String val age: Int } data class Student( override val name: String, override val surname: String, override val age: Int, val university: String ) : Person data class Worker( override val name: String, override val surname: String, override val age: Int, val company: String ) : Person fun main() { val json: List<PersonJson> = listOf(/* ... */) val people = json.map { if (it.type == "student") Student( it.name, it.surname, it.age, it.university!! ) else Worker( it.name, it.surname, it.age, it.company!! ) } println(people.joinToString { "${it.name} ${it.surname}" }) } interface Person { val name: String val surname: String val age: Int } data class PersonData( override val name: String, override val surname: String, override val age: Int ) : Person data class Student( val data: PersonData, val university: String ) : Person by data data class Worker( val data: PersonData, val company: String ) : Person by data fun main() { val json: List<PersonJson> = listOf(/* ... */) val people = json.map { val data = PersonData( it.name, it.surname, it.age ) if (it.type == "student") Student(data, it.university!!) else Worker(data, it.company!!) } println(people.joinToString { "${it.name} ${it.surname}" }) } DelegationInheritance
  • 86. interface Person { val name: String val surname: String val age: Int val address: String val city: String val zipCode: String val nation: String val telephoneNumber1: String val telephoneNumber2: String val telephoneNumber3: String } data class Student( override val name: String, override val surname: String, override val age: Int, val university: String, override val address: String, override val city: String, override val zipCode: String, override val nation: String, override val telephoneNumber1: String, override val telephoneNumber2: String, override val telephoneNumber3: String ) : Person data class Worker( override val name: String, override val surname: String, override val age: Int, val company: String, override val address: String, override val city: String, override val zipCode: String, override val nation: String, override val telephoneNumber1: String, override val telephoneNumber2: String, override val telephoneNumber3: String ) : Person data class Unemployed( override val name: String, override val surname: String, override val age: Int, override val address: String, override val city: String, override val zipCode: String, override val nation: String, override val telephoneNumber1: String, override val telephoneNumber2: String, override val telephoneNumber3: String ) : Person fun main() { val json: List<PersonJson> = listOf(/* ... */) val people = json.map { if (it.type == "student") Student( it.name, it.surname, it.age, it.university!!, it.address, it.city, it.zipCode, it.nation, it.telephoneNumber1, it.telephoneNumber2, it.telephoneNumber3 ) else if (it.type == "worker") Worker( it.name, it.surname, it.age, it.company!!, it.address, it.city, it.zipCode, it.nation, it.telephoneNumber1, it.telephoneNumber2, it.telephoneNumber3 ) else Unemployed( it.name, it.surname, it.age, it.address, it.city, it.zipCode, it.nation, it.telephoneNumber1, it.telephoneNumber2, it.telephoneNumber3 ) } println(people.joinToString { "${it.name} ${it.surname}" }) } interface Person { val name: String val surname: String val age: Int val address: String val city: String val zipCode: String val nation: String val telephoneNumber1: String val telephoneNumber2: String val telephoneNumber3: String } data class PersonData( override val name: String, override val surname: String, override val age: Int, override val address: String, override val city: String, override val zipCode: String, override val nation: String, override val telephoneNumber1: String, override val telephoneNumber2: String, override val telephoneNumber3: String ) : Person data class Student( val data: PersonData, val university: String ) : Person by data data class Worker( val data: PersonData, val company: String ) : Person by data data class Unemployed( val data: PersonData ) : Person by data fun main() { val json: List<PersonJson> = listOf(/* ... */) val people = json.map { val data = PersonData( it.name, it.surname, it.age, it.address, it.city, it.zipCode, it.nation, it.telephoneNumber1, it.telephoneNumber2, it.telephoneNumber3 ) if (it.type == "student") Student(data, it.university!!) else if (it.type == "worker") Worker(data, it.company!!) else Unemployed(data) } println(people.joinToString { "${it.name} ${it.surname}" }) } DelegationInheritance
  • 87. Wrappingup “” “The ratio of time spent reading (code) versus writing is well over 10 to 1 (therefore) making it easy to read makes it easier to write Robert C. Martin
  • 88. Wrappingup “” “The Principle of Least Astonishment states that the result of performing some operation should be obvious, consistent, and predictable, based upon the name of the operation and other clues https://wiki.c2.com/?PrincipleOfLeastAstonishment
  • 89. Wrappingup Delegates can be useful to simplify code but there are pros and cons!
  • 90. Links&contacts Simpler Kotlin class hierarchies using class delegation proandroiddev.com/simpler-kotlin-class-hierarchies-using-class-delegation-35464106fed5 Kotlin delegates in Android development — Part 1 medium.com/hackernoon/kotlin-delegates-in-android-development-part-1-50346cf4aed7 Kotlin delegates in Android development — Part 2 proandroiddev.com/kotlin-delegates-in-android-development-part-2-2c15c11ff438 @fabioCollini linkedin.com/in/fabiocollini github.com/fabioCollini medium.com/@fabioCollini