Superpowers of Kotlin delegation

@marcinmoskala

@marcinmoskala

@marcinmoskala

@marcinmoskala

@marcinmoskala

https://www.pinterest.de/explore/alter-computer/

@marcinmoskala

Delegation pattern

interface Player {
  fun playGame()
}

class RpgGamePlayer(val enemy: String) : Player {
  override fun playGame() {
     println("Killing $enemy")
  }
}

class WitcherPlayer(enemy: String) : Player {
  val player = RpgGamePlayer(enemy)
  
  override fun playGame() {
     player.playGame()
  }
}

RpgGamePlayer("monsters").playGame() // Prints: Killing monsters
WitcherPlayer("monsters").playGame() // Prints: Killing monsters

WitcherPlayer class is delegating methods defined in Player interface to an instance of type RpgGamePlayer

@marcinmoskala

Inheritance

interface Player {
  fun playGame()
}

class RpgGamePlayer(val enemy: String) : Player {
  override fun playGame() {
     println("Killing $enemy")
  }
}

class WitcherPlayer(enemy: String) : RpgGamePlayer(enemy)

RpgGamePlayer("monsters").playGame() // Prints: Killing monsters
WitcherPlayer("monsters").playGame() // Prints: Killing monsters

@marcinmoskala

Delegation pattern

@marcinmoskala

Inheritance breaks encapsulation

public class CountingHashSet extends HashSet {

    private int addCount = 0;

    @Override
    public boolean add(Object o) {
        addCount += 1;
        return super.add(o);
    }

    @Override
    public boolean addAll(Collection collection) {
        addCount += collection.size();
        return super.addAll(collection);
    }

    public int getAddCount() {
        return addCount;
    }
}

// Usage
CountingHashMap s = new CountingHashMap();
s.addAll(Arrays.asList(new String[]{"A", "B", "C"}));
System.out.println(s.getAddCount()); // Prints: 6

@marcinmoskala

When should we use delegation instead of inheritance?

When classes is final, private or not designed for inheritance.

When there is no "is-a" relationship

When we need only some functionalities from class

@marcinmoskala

Kotlin Class Delegation

interface Player {
  fun playGame()
}

class RpgGamePlayer(val enemy: String) : Player {
  override fun playGame() {
     println("Killing $enemy")
  }
}

class WitcherPlayer(enemy: String) : Player by RpgGamePlayer(enemy)

RpgGamePlayer("monsters").playGame() // Prints: Killing monsters
WitcherPlayer("monsters").playGame() // Prints: Killing monsters

Using by keyword, we are informing compiler to delegate all methods defined in Player interface from WitcherPlayer to RpgGamePlayer

@marcinmoskala

Kotlin Class Delegation

class WitcherPlayer() : Player by RpgGamePlayer("monsters")

class WitcherPlayer(enemy: String) : Player by RpgGamePlayer(enemy)

class WitcherPlayer(player: Player) : Player by player

class WitcherPlayer(val player: Player = RpgGamePlayer("monsters")) : Player by player

val player = RpgGamePlayer(10)
class WitcherPlayer() : Player by player

@marcinmoskala

Decorator pattern

InputStream fis = new FileInputStream("/someFile.gz");
InputStream bis = new BufferedInputStream(fis);
InputStream gis = new ZipInputStream(bis);
ObjectInputStream ois = new ObjectInputStream(gis);
SomeObject someObject = (SomeObject) ois.readObject();
class PlayHard(player: Player) : Player by player {

  override fun playGame() {
     println("Arrgggh! ")
     player.playGame()
  }
}


// Usage
val player = WitcherPlayer("monsters")
player.playGame() // Prints: Killing monsters

val hardcore = PlayHard(player)
hardcore.playGame() // Prints: Arrgggh! Killing monsters

@marcinmoskala

Property delegation

class User(val name: String, val surname: String)

var user: User by UserDelegate()

// Usage
println(user.name)
user = User(“Marcin”,”Moskala”) 

Property user is delegated to object of type UserDelegate.

@marcinmoskala

@marcinmoskala

public class MainActivity extends Activity {

    @BindView(R.id.title) TextView title;
    @BindView(R.id.subtitle) TextView subtitle;
    @BindView(R.id.footer) TextView footer;

    // ...
}
public class MainPresenter implements Presenter {

    @Inject NetworkApi networkApi;
    @Inject UserService userService;
 
    // ...
}
@Entity
@Table(name = "EMPLOYEE")
public class Employee {
   @Id @GeneratedValue
   @Column(name = "id")
   private int id;

   @Column(name = "first_name")
   private String firstName;

   @Column(name = "last_name")
   private String lastName;

   @Column(name = "salary")
   private int salary;  
}
private UserRepository userRepo;

public UserRepository getUserRepo() {
    if(userRepo == null) {
        userRepo = new UserRepository();
    }
    return userRepo;
}

private List<String> observableList;

public void setObservableList(List<String> observableList) {
    this.observableList = observableList;
    onObservableListChanged()
}

Need for property delegates

@marcinmoskala

Field vs Property

Field

Property

// Java

public String description;
// Kotlin

var description: String? = null

@marcinmoskala

Property

Property

// Java

private String description;

String getDescription() {
    return description;
}

void setDescription(String description) {
    this.description = description;
}
// Kotlin

var description: String? = null

Property

@marcinmoskala

Property

Property

// Java

private String description;

String getDescription() {
    return Strings.trim(description);
}

void setDescription(String description) {
    if(Strings.isNotNullOrEmpty(description))
        this.description = description;
}
// Kotlin

var description: String? = null
    get() = field?.trim()
    set(value) {
        if (value != null && value.isNotEmpty())
            field = value
    }

Property

@marcinmoskala

Property

Property

// Java

String getDescription() {
    return "Super presentation";
}
// Kotlin

val description: String? = null
    get() = "Super presentation"

Property

@marcinmoskala

class User(val name: String, val surname: String)

var user: User by UserDelegate()

// Usage
println(user.name)
user = User(“Marcin”,”Moskala”) 

Property user is delegated to object of type UserDelegate.

var p$delegate = UserDelegate()
var user: User
  get() = p$delegate.getValue(this, this::user)
  set(value) {
    p$delegate.setValue(this, this::user, value)
  }

Property delegation

@marcinmoskala

data class User(val name: String, val surname: String)

class UserDelegate {

    operator fun getValue(thisRef: Any?, property: KProperty<*>): User
            = User("Marcin", "Moskala")

    operator fun setValue(thisRef: Any?, property: KProperty<*>, user: User) {
        println("I am not setting new user")
    }
}

fun main(args: Array<String>) {
    var user: User by UserDelegate()

    println(user) // Prints: User(name=Marcin, surname=Moskala)
    user = User("Michal", "Michalski") // Prints: I am not setting new user
}

Property delegation

@marcinmoskala

val someProperty by object {
  operator fun  getValue(thisRef: Any?, property: KProperty<*>) = "Something"
}
println(someProperty) // Prints: Something

object as a property delegate

@marcinmoskala

var user$delegate = UserDelegate()
var user: User
  get() = user$delegate.getValue(this, this::user)
  set(value) {
    user$delegate.setValue(this, this::user, value)
  }
public interface ReadOnlyProperty<in R, out T> {
  public operator fun getValue(thisRef: R, property: KProperty<*>): T
}

public interface ReadWriteProperty<in R, T> {
   public operator fun getValue(thisRef: R, property: KProperty<*>): T
   public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

Specify allowed location

Specify allowed

type

Property delegation

@marcinmoskala

var a by SomeDelegate() // Top-level property with delegate

fun someTopLevelFun() {
  var b by SomeDelegate() // Local variable (inside top-level function) with delegate
}

class SomeClass() {

  var c by SomeDelegate() // Member property with delegate
  
  fun someMethod() {
    val d by SomeDelegate() // Local variable (inside method) with delegate
  }
}

Property delegation

@marcinmoskala

var name: String by Delegates.notNull()
private class NotNullVar<T: Any>() : ReadWriteProperty<Any?, T> {
    private var value: T? = null

    public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException(
            "Property ${property.name} should be initialized before get."
        )
    }

    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = value
    }
}
name = "Marcin"
println(name) // Prints: Marcin
name = "Maja"
println(name) // Prints: Maja
println(name) // Error: 
// Property name should be 
// initialized before get.

notNull

@marcinmoskala

var user: User by Delegates.notNull()
lateinit var user: User

 

Can be used everywhere

Can be used to all types

More efficient

Cannot be used at top level

Cannot be used to primitive (Int, Long, Double etc.)

notNull vs lateinit

@marcinmoskala

val userRepo by lazy { UserRepository() }
private var _userRepo: UserRepository? = null
private val userRepoLock = Any()
val userRepo: UserRepository
   get() {
       synchronized(userRepoLock) {
           if (_userRepo == null) {
               _userRepo = UserRepository()
           }
           return _userRepo!!
       }
   }
val userRepo by lazy(LazyThreadSafetyMode.NONE) { UserRepository() }

lazy

@marcinmoskala

class MainActivity : Activity() {

    lateinit var questionLabelView: TextView
    lateinit var answerLabelView: EditText
    lateinit var confirmButtonView: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        questionLabelView = findViewById(R.id.main_question_label) as TextView
        answerLabelView = findViewById(R.id.main_answer_label) as EditText
        confirmButtonView = findViewById(R.id.main_button_confirm) as Button
    }
}

lazy

@marcinmoskala

class MainActivity : Activity() {

  var questionLabelView: TextView by lazy { findViewById(R.id.main_question_label) }
  var answerLabelView: TextView by lazy { findViewById(R.id.main_answer_label) }
  var confirmButtonView: Button by lazy { findViewById(R.id.main_button_confirm) }

  override fun onCreate(savedInstanceState: Bundle) {
     super.onCreate(savedInstanceState)
     setContentView(R.layout.main_activity)
  }
}
  1. Property is declared and initialized in single place
  2. Properties are not lateinint and not nullable
  3. Properties are read only
  4. The value will be resolved only when the property is accessed for the first time.
  5. Not used property will be marked by the compiler

lazy

@marcinmoskala

class MainActivity : Activity() {

  var questionLabelView: TextView by bindView(R.id.main_question_label)
  var answerLabelView: TextView by bindView(R.id.main_answer_label)
  var confirmButtonView: Button by bindView(R.id.main_button_confirm)

  override fun onCreate(savedInstanceState: Bundle) {
     super.onCreate(savedInstanceState)
     setContentView(R.layout.main_activity)
  }
}
fun <T: View> Activity.bindView(viewId: Int) = lazy { findViewById<T>(viewId) }

lazy

@marcinmoskala

class SettingsActivity : Activity() {

  private val titleString by bindString(R.string.title)
  private val blueColor by bindColor(R.color.blue)

  private val doctor by extra<Doctor>(DOCTOR_KEY)
  private val title by extraString(TITLE_KEY)

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.settings_activity)
  }
}
fun <T> Activity.bindString(@IdRes id: Int): Lazy<T> = lazy { getString(id) }

fun <T> Activity.bindColour(@IdRes id: Int): Lazy<T> = lazy { getColour(id) }

fun <T : Parcelable> Activity.extra(key: String) = lazy { intent.extras.getParcelable<T>(key) }

fun Activity.extraString(key: String) = lazy { intent.extras.getString(key) }

lazy

@marcinmoskala

class MainActivity : Activity(), MainView {

  private val doctor by extra<Doctor>(DOCTOR_KEY)
  private val title by extraString(TITLE_KEY)

  private val titleView by bindView<TextView>(R.string.title_view)

  private val presenter by lazy { MainPresenter(this, doctor) }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.settings_activity)
    titleView.text = title
  }
}

lazy

@marcinmoskala

var name: String by Delegates.observable("Empty"){ property, oldValue, newValue ->
  println("$oldValue -> $newValue")
}

// Usage
name = "Martin" // Prints: Empty -> Martin
name = "Igor" // Prints: Martin -> Igor
name = "Igor" // Prints: Igor -> Igor

observable

@marcinmoskala

var list: List<LocalDate> by observable(list) { _, old, new ->
  if(new != old) notifyDataSetChanged()
}
private var drawerOpen by Delegates.observable(false) { _, _, opened ->
    if (opened) drawerLayout.openDrawer(GravityCompat.START)
    else drawerLayout.closeDrawer(GravityCompat.START)
}
var presenters: List<Presenter> by observable(list) { _, old, new ->
  (old - new).forEach { it.onDestroy() }
  (new - old).forEach { it.onCreate() }
}

observable

@marcinmoskala

var list: List<String> by Delegates.vetoable(emptyList()) { prop, old, new -> 
   if(new.size < 3) return@vetoable false
   println(new)
   true
}

// Usage
listVetoable = listOf("A", "B", "C") // Update A, B, C
println(listVetoable) // Prints: [A, B, C]
listVetoable = listOf("A") // Nothing happens
println(listVetoable) // Prints: [A, B, C]
listVetoable = listOf("A", "B", "C", "D", "E")  // Prints: [A, B, C, D, E]
var name: String by Delegates.vetoable("") { prop, old, new -> 
  if (isValid(new)) {
    showNewName(new)
    true
  } else {
    showNameError()
    false
  }
}

vetoable

@marcinmoskala

class User(map: Map<String, Any>) {
  val name: String by map
  val kotlinProgrammer: Boolean by map
}

// Usage
val map: Map<String, Any> = mapOf(
    "name" to "Marcin",
    "kotlinProgrammer" to true
)
val user = User(map)
println(user.name)  // Prints: Marcin
println(user.kotlinProgrammer)  // Prints: true
operator fun <V, V1: V> Map<String, V>.getValue(
      thisRef: Any?,
      property: KProperty<*>
): V1 {
  val key = property.name
  val value = get(key)
  if (value == null && !containsKey(key)) {
    throw NoSuchElementException("Key ${property.name} is missing in the map.")
  } else {
    return value as V1
  }
}

Map as a property delegate

@marcinmoskala

class User(val map: MutableMap<String, Any>) {
  var name: String by map
}

// Usage
val map = mutableMapOf("name" to "Marcin")
val user = User(map)
println(user.name) // Prints: Marcin
user.map.put("name", "Igor")
println(user.name) // Prints: Igor
user.name = "Michal"
println(user.name) // Prints: Michal

MutableMap as a property delegate

@marcinmoskala

fun <T> mutableLazy(initializer: () -> T): ReadWriteProperty<Any?, T> 
      = MutableLazy<T>(initializer)

private class MutableLazy<T>(val initializer: () -> T) : ReadWriteProperty<Any?, T> {

   private var value: T? = null
   private var initialized = false

   override fun getValue(thisRef: Any?, property: KProperty<*>): T {
       synchronized(this) {
           if (!initialized) {
               value = initializer()
               initialized = true
           }
           return value as T
       }
   }

   override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
       synchronized(this) {
           this.value = value
           initialized = true
       }
   }
}

mutable lazy

@marcinmoskala

var gameMode : GameMode by mutableLazy {
    getDefaultGameMode()
}

var mapConfiguration : MapConfiguration by mutableLazy {
    getSavedMapConfiguration()
}

var screenResolution : ScreenResolution by mutableLazy {
    getOptimalScreenResolutionForDevice()
}
public class MainActivity: BaseActivity() {

    @get:Arg var userData: String by argExtra()
}

mutable lazy

@marcinmoskala

Source: spindrift-racing.com/jules-verne/drupal/en/log-book/icebergs-en

interface LoginView {
    fun showProgress(show: Boolean)
    fun getEmail(): String
    fun setEmail(text: String)
    fun getPassword(): String
    fun setPassword(text: String)
    fun setEmailError(id: Int?)
    fun setPasswordError(id: Int?)
    fun requestEmailFocus()
}

View binding

@marcinmoskala

override fun getEmail(): String = emailView.text.toString()

override fun setEmail(text: String) {
    emailView.text = text
}

override fun getPassword(): String = passwordView.text.toString()

override fun setPassword(text: String) {
    passwordView.text = text
}

override fun setEmailError(id: Int?) {
    emailView.setErrorId(id)
}

override fun setPasswordError(id: Int?) {
    passwordView.setErrorId(id)
}

override fun requestEmailFocus() {
    emailView.requestFocus()
}

override fun requestPasswordFocus() {
    passwordView.requestFocus()
}

override fun showProgress(show: Boolean) {
    progressView.visibility = if (show) View.VISIBLE else View.GONE
    loginFormView.visibility = if (show) View.GONE else View.VISIBLE
}

View binding

@marcinmoskala

override var progressVisible by bindToLoading(R.id.progressView, R.id.loginFormView)
override var email by bindToText(R.id.emailView)
override var password by bindToText(R.id.passwordView)
override var emailErrorId by bindToErrorId(R.id.emailView)
override var passwordErrorId by bindToErrorId(R.id.passwordView)
override val emailRequestFocus by bindToRequestFocus(R.id.emailView)
override val passwordRequestFocus by bindToRequestFocus(R.id.passwordView)

https://github.com/MarcinMoskala/KotlinAndroidViewBindings

interface LoginView {
    var progressVisible: Boolean
    var email: String
    var password: String
    var emailErrorId: Int?
    var passwordErrorId: Int?
    val emailRequestFocus: () -> Unit
    val passwordRequestFocus: () -> Unit
}

View binding

interface LoginView {
    fun showProgress(show: Boolean)
    fun getEmail(): String
    fun setEmail(text: String)
    fun getPassword(): String
    fun setPassword(text: String)
    fun setEmailError(id: Int?)
    fun setPasswordError(id: Int?)
    fun requestEmailFocus()
    fun requestPasswordFocus()
}

https://github.com/MarcinMoskala/KotlinAndroidViewBindings

fun Activity.bindToEditText(@IdRes editTextId: Int): ReadWriteProperty<Any?, String>
        = bindToEditText { findViewById(editTextId) as EditText }

private fun bindToEditText(viewProvider: () -> EditText): ReadWriteProperty<Any?, String>
        = EditTextViewTextBinding(lazy(viewProvider))

private class EditTextViewTextBinding(
    lazyViewProvider: Lazy<EditText>
) : ReadWriteProperty<Any?, String> {

    val view by lazyViewProvider

    override fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return view.text.toString()
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        view.setText(value)
    }
}

View binding

val CAN_EAT_PIE_KEY = "canEatPieKey"

val editor = preferences.edit();
editor.putBoolean(CAN_EAT_PIE_KEY, true);
editor.commit();

if(preferences.getBoolean(CAN_EAT_PIE_KEY, false)) {
  // Code
}
val Context.preferences: SharedPreferences
    get() = PreferenceManager.getDefaultSharedPreferences(this)
preferences.canEatPie = true
if(preferences.canEatPie) {
  // Code
}

Preference binding

@marcinmoskala

https://github.com/MarcinMoskala/KotlinPreference

Preference binding

var SharedPreferences.canEatPie: Boolean by bindToPreferenceField(true, "CanEatPieKey")

var SharedPreferences.experience: Float? by bindToPreferenceFieldNullable()
var SharedPreferences.allPieInTheWorld: Long by bindToPreferenceField(0, "AllPieKey")
var SharedPreferences.savedGame: Game? by bindToPreferenceFieldNullable()

// Usage
stopDietButton.setOnClickListener { 
    preferences.canEatPie = true 
}

startDietButton.setOnClickListener { 
    preferences.canEatPie = false 
}

eatPieButton.setOnClickListener {
    if(preferences.canEatPie) {
        toast("Heah, pie :D")
    } else {
        toast("I cannot? :(")
    }
}

@marcinmoskala

https://github.com/MarcinMoskala/PreferenceHolder

Preference binding

object Pref: PreferenceHolder() {
    var canEatPie: Boolean by bindToPreferenceField(true, "CanEatPieKey")

    var longList: Map<Int, Long> by bindToPreferenceField(mapOf(0 to 12L, 10 to 143L))
    var savedGame: Game? by bindToPreferenceFieldNullable("GameKey")
}

// Usage
stopDietButton.setOnClickListener { 
    Pref.canEatPie = true 
}
startDietButton.setOnClickListener { 
    Pref.canEatPie = false 
}
eatPieButton.setOnClickListener {
    if(Pref.canEatPie) {
        toast("Heah, pie :D")
    } else {
        toast("I cannot? :(")
    }
}

@marcinmoskala

What property delegates really introduced?

@marcinmoskala

override var email by bindToText(R.id.emailView)

Property delegation vs functions

private var _userRepo: UserRepository? = null
private val userRepoLock = Any()
val userRepo: UserRepository
   get() {
       synchronized(userRepoLock) {
           if (_userRepo == null) {
               _userRepo = UserRepository()
           }
           return _userRepo!!
       }
   }
val userRepo by lazy { UserRepository() }
val emailView by lazy { findViewById(R.is.email_view) }

override fun getEmail(): String 
      = emailView.text.toString()

override fun setEmail(text: String) {
    emailView.text = text
}

vs

vs

@marcinmoskala

override var email: String by bindToText(R.id.emailView)

email = "marcinmoskala@gmail.com"
if(email.matches(EMAIL_REGEX)) { /* ... */ }

interface MainView {
    var email: String
}

Property delegation vs classes

val userRepo: LazyProperty<UserRepository> 
      = LazyProperty { UserRepository() } 

userRepo.getValue().getUser()
val userRepo: UserRepository by lazy { UserRepository() }

userRepo.getUser()
override var email: BindToText = BindToText(R.id.emailView)

email.setValue("marcinmoskala@gmail.com")
if(email.getValue().matches(EMAIL_REGEX)) { /* ... */ }

interface MainView {
    var email: BindToText
}

vs

vs

@marcinmoskala

http://kotlinacademy.org/

@marcinmoskala

About me

Marcin Moskała

 

marcinmoskala.com

blog.kotlin-academy.com

github.com/MarcinMoskala

@marcinmoskala

 

Senior Android Developer in  Gamekit, writing articles, open-source, freelancing and consulting.

 

marcinmoskala@gmail.com

WDI: Superpowers of Kotlin delegation

By Marcin Moskala

WDI: Superpowers of Kotlin delegation

Delegation is one of the most inconspicuous Kotlin features. Property delegation is still pretty new feature in the programming world and most developers still use only small amount of possibilities it provides. On the other hand, class delegation is known for decades and it was promoted by big authorities. All it needed was language support, which was introduced in Kotlin. On this session, we are going to dive into possibilities that Kotlin delegation gives. Let's discover this features and understand its superpowers.

  • 652