Slide 1

Slide 1 text

Reactive approach to delegation in Kotlin Aida Issayeva @aida_isay

Slide 2

Slide 2 text

has a chimney has a window has a door

Slide 3

Slide 3 text

interface WindowActionListener { void openWindow(); void closeWindow(); } interface DoorActionListener { void closeDoor(); void openDoor(); }

Slide 4

Slide 4 text

Delegate classes class Window implements WindowActionListener { @Override public void openWindow() { } @Override public void closeWindow() { } } class Door implements DoorActionListener { @Override public void closeDoor() { } @Override public void openDoor() { } }

Slide 5

Slide 5 text

Delegation in Java class Room implements WindowActionListener, DoorActionListener { private Window window = new Window(); private Door door = new Door(); @Override public void openWindow() { window.openWindow(); } @Override public void closeWindow() { window.closeWindow(); } @Override public void closeDoor() { door.closeDoor(); } @Override public void openDoor() { door.openDoor(); } }

Slide 6

Slide 6 text

Delegation in Java class LivingRoom implements WindowActionListener { private Window window = new Window(); @Override public void openWindow() { window.openWindow(); } @Override public void closeWindow() { window.closeWindow(); } }

Slide 7

Slide 7 text

“The Delegation pattern has proven to be a good alternative to implementation inheritance, and Kotlin supports it natively requiring zero boilerplate code” https://kotlinlang.org/docs/reference/delegation.html

Slide 8

Slide 8 text

Delegation in Kotlin class Room : WindowActionListener by Window(), DoorActionListener by Door() class LivingRoom : WindowActionListener by Window()

Slide 9

Slide 9 text

Delegation in Kotlin class Room : WindowActionListener by Window(), DoorActionListener by Door() class LivingRoom : WindowActionListener by Window()

Slide 10

Slide 10 text

Implicit delegation Translation public final class Room implements WindowActionListener, DoorActionListener { // $FF: synthetic field private final Window $$delegate_0 = new Window(); // $FF: synthetic field private final Door $$delegate_1 = new Door(); public void closeWindow() { this.$$delegate_0.closeWindow(); } public void openWindow() { this.$$delegate_0.openWindow(); } public void closeDoor() { this.$$delegate_1.closeDoor(); } public void openDoor() { this.$$delegate_1.openDoor(); } }

Slide 11

Slide 11 text

Implicit delegation Translation public final class LivingRoom implements WindowActionListener { // $FF: synthetic field private final Window $$delegate_0 = new Window(); public void closeWindow() { this.$$delegate_0.closeWindow(); } public void openWindow() { this.$$delegate_0.openWindow(); } } Tools->Kotlin->Show Kotlin Bytecode -> “Decompile” button

Slide 12

Slide 12 text

Delegated properties class User(val firstName: String, val lastName: String) { var userName: String by UserNameDelegate() }

Slide 13

Slide 13 text

Create custom delegated properties //implement interfaces //for val class UserNameDelegate : ReadOnlyProperty { override fun getValue(thisRef: User, property: KProperty<*>): String { return "${thisRef.firstName}_${thisRef.lastName}" } }

Slide 14

Slide 14 text

//implement interfaces //for var class UserNameDelegate : ReadWriteProperty { override fun getValue(thisRef: User, property: KProperty<*>): String { return "${thisRef.firstName}_${thisRef.lastName}" } override fun setValue(thisRef: User, property: KProperty<*>, value: String) { println("$value has been assigned to '${property.name}' in $thisRef.") } } Create custom delegated properties

Slide 15

Slide 15 text

//or use `operator` //for val class UserNameDelegate { operator fun getValue(thisRef: User?, property: KProperty<*>): String { return "${thisRef?.firstName}_${thisRef?.lastName}" } } Create custom delegated properties

Slide 16

Slide 16 text

//or use `operator` //for var class UserNameDelegate { operator fun getValue(thisRef: User?, property: KProperty<*>): String { return "${thisRef?.firstName}_${thisRef?.lastName}" } operator fun setValue(thisRef: User?, property: KProperty<*>, value: String) { println(“$value has been assigned to '${property.name}' in $thisRef.") } } Create custom delegated properties

Slide 17

Slide 17 text

Delegated properties class CustomDelegatedPropertyActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity) val user = User("Aida", "Issayeva") println("username:" + user.userName) } } I/System.out: username:Aida_Issayeva

Slide 18

Slide 18 text

1. lazy

Slide 19

Slide 19 text

1. lazy delegate class AboutMeFragment : Fragment() { val component by lazy { val appComponent = DaggerHelper.getAppComponent(this) AboutMeComponent.builder() .appComponent(appComponent) .build() } }

Slide 20

Slide 20 text

Lazy delegate class AboutMeFragment : Fragment() { val component by lazy { val appComponent = DaggerHelper.getAppComponent(this) AboutMeComponent.builder() .appComponent(appComponent) .build() } }

Slide 21

Slide 21 text

Lazy delegate class AboutMeFragment : Fragment() { val component by lazy { val appComponent = DaggerHelper.getAppComponent(this) AboutMeComponent.builder() .appComponent(appComponent) .build() } }

Slide 22

Slide 22 text

Lazy delegate class AboutMeFragment : Fragment() { val component by lazy { val appComponent = DaggerHelper.getAppComponent(this) AboutMeComponent.builder() .appComponent(appComponent) .build() } val viewModel by lazy { val factory = AboutMeViewModel.Factory(component) ViewModelProviders.of(this, factory) .get(AboutMeViewModel::class.java) } }

Slide 23

Slide 23 text

init val init val init val ? LazyThreadSafetyMode SYNCHRONIZED PUBLICATION NONE

Slide 24

Slide 24 text

lazy • Faster class init •No more useless null checks •Smart cast and thread sync •Not used property marked by compiler •Declared and init in single place

Slide 25

Slide 25 text

2. notNull

Slide 26

Slide 26 text

2. notNull delegate class AboutMeFragment : Fragment() { lateinit var age: Int }

Slide 27

Slide 27 text

notNull delegate class AboutMeFragment : Fragment() { var age: Int by Delegates.notNull() } import kotlin.properties.Delegates.notNull class AboutMeFragment : Fragment() { var age: Int by notNull() }

Slide 28

Slide 28 text

notNull •Later init •Primitive && top-level properties • No null checks

Slide 29

Slide 29 text

3. map

Slide 30

Slide 30 text

3. map delegate class Profile(map: Map) { val firstName: String by map val lastName: String by map val phoneNumber: String by map val income: Double by map }

Slide 31

Slide 31 text

map delegate class Profile(map: Map) { val firstName: String by map val lastName: String by map val phoneNumber: String by map val income: Double by map }

Slide 32

Slide 32 text

map delegate override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val map = mapOf( "firstName" to "Aida", "middleName" to null, "lastName" to "Issayeva", "phoneNumber" to "212-212-2121", "income" to 1000000000.00 ) val person = Profile(map) println(person.firstName) }

Slide 33

Slide 33 text

map •Simplify access • Define expected structure • But have a power to access unexpected fields •Map key == property name

Slide 34

Slide 34 text

4. observable

Slide 35

Slide 35 text

4. observable delegate class AboutMeFragment : Fragment() { val disposables = CompositeDisposable() var isLoading by Delegates.observable(false) { p, old, new -> buttonDelete.isEnabled = !new buttonSubmit.isEnabled = !new progressBar.setVisibleOrGone(new) } . . . super.onViewCreated(view, savedInstanceState) buttonSubmit.setOnClickListener { isLoading = true viewModel.updateProfile("Aida", "Issayeva", “[email protected]“) .subscribe { isLoading = false } .addTo(disposables) } } }

Slide 36

Slide 36 text

Observable delegate class AboutMeFragment : Fragment() { val disposables = CompositeDisposable() var isLoading by Delegates.observable(false) { p, old, new -> buttonDelete.isEnabled = !new buttonSubmit.isEnabled = !new progressBar.setVisibleOrGone(new) } . . . super.onViewCreated(view, savedInstanceState) buttonSubmit.setOnClickListener { isLoading = true viewModel.updateProfile("Aida", "Issayeva", “[email protected]”) .subscribe { isLoading = false } .addTo(disposables) } } }

Slide 37

Slide 37 text

Observable delegate class AboutMeFragment : Fragment() { val disposables = CompositeDisposable() var isLoading by Delegates.observable(false) { p, old, new -> buttonDelete.isEnabled = !new buttonSubmit.isEnabled = !new progressBar.setVisibleOrGone(new) } . . . super.onViewCreated(view, savedInstanceState) buttonSubmit.setOnClickListener { isLoading = true viewModel.updateProfile("Aida", "Issayeva", “[email protected]”) .subscribe { isLoading = false } .addTo(disposables) } } }

Slide 38

Slide 38 text

Observable delegate class AboutMeFragment : Fragment() { val disposables = CompositeDisposable() var isLoading by Delegates.observable(false) { p, old, new -> buttonDelete.isEnabled = !new buttonSubmit.isEnabled = !new progressBar.setVisibleOrGone(new) } . . . super.onViewCreated(view, savedInstanceState) buttonSubmit.setOnClickListener { isLoading = true viewModel.updateProfile("Aida", "Issayeva", “[email protected]”) .subscribe { isLoading = false } .addTo(disposables) } } }

Slide 39

Slide 39 text

Adapters in Java public class NameAdapter extends RecyclerView.Adapter { public void updateNames(ArrayList names){ this.list = names; notifyDataSetChanged(); } }

Slide 40

Slide 40 text

Adapters in Kotlin class NameAdapter(val context: Context) : RecyclerView.Adapter() { var list = emptyList() fun updateNames(names: ArrayList) { list = names notifyDataSetChanged() } }

Slide 41

Slide 41 text

Adapters in Kotlin class NameAdapter(val context: Context) : RecyclerView.Adapter() { var list by observable(mutableListOf()) { _, old, new -> if (old != new) notifyDataSetChanged() } }

Slide 42

Slide 42 text

Observable • Lambda is called after new value set •Have an access to old value •Notify ui immediately about changes in data • Advantage of partial ui update

Slide 43

Slide 43 text

5. vetoable

Slide 44

Slide 44 text

Adapters in Java public class Adapter extends RecyclerView.Adapter { public void updateNames(ArrayList names){ if(names.size() <= 5) { this.list = names; notifyDataSetChanged(); } } }

Slide 45

Slide 45 text

Adapters in Kotlin class NameAdapter(val context: Context) : RecyclerView.Adapter() { var list by Delegates.vetoable>(mutableListOf()) { _, old, new -> new.size <= 5 } }

Slide 46

Slide 46 text

Vetoable delegate class NameAdapter(val context: Context) : RecyclerView.Adapter() { var list by Delegates.vetoable>(mutableListOf()) { _, old, new -> if (new.size <= 5) { notifyDataSetChanged() return@vetoable true } return@vetoable false } }

Slide 47

Slide 47 text

Vetoable delegate class MainActivity : Activity() { var firstName: String by Delegates.vetoable("") { prop, old, new -> when (new.length) { 0 -> hideValidation() in 2..20 -> showValidData(new) else -> showError() } new.startsWith("a") } }

Slide 48

Slide 48 text

vetoable •Lambda is called before new value set • To set or not to set •Have an access to old value • Notify ui immediately about changes in data • Advantage of partial ui update

Slide 49

Slide 49 text

Delegated properties in custom views

Slide 50

Slide 50 text

override fun onCreate(savedInstanceState: Bundle?) { . . . buttonLike.setOnClickListener { likePost() } buttonSend.setOnClickListener { sendComment() } editText.addTextChangedListener(object : TextWatcher { . . . override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { when (count) { 0 -> { buttonLike.visibility = View.VISIBLE buttonSend.visibility = View.GONE } 1 -> { buttonLike.visibility = View.GONE buttonSend.visibility = View.VISIBLE } } } }) } First approach:

Slide 51

Slide 51 text

override fun onCreate(savedInstanceState: Bundle?) { . . . buttonLike.setOnClickListener { likePost() } buttonSend.setOnClickListener { sendComment() } editText.addTextChangedListener(object : TextWatcher { . . . override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { when (count) { 0 -> { buttonLike.visibility = View.VISIBLE buttonSend.visibility = View.GONE } 1 -> { buttonLike.visibility = View.GONE buttonSend.visibility = View.VISIBLE } } } }) }

Slide 52

Slide 52 text

editText.addTextChangedListener(object : TextWatcher { . . . override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { when (count) { 0 -> { isInputEmpty = true buttonLike.setBackgroundResource(R.drawable.ic_favorite_gray_24dp) } 1 -> { isInputEmpty = false buttonLike.setBackgroundResource(R.drawable.ic_send_24dp) } } } }) buttonLike.setOnClickListener { if (isInputEmpty) likePost() else sendComment() } Second approach:

Slide 53

Slide 53 text

editText.addTextChangedListener(object : TextWatcher { . . . override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { when (count) { 0 -> { isInputEmpty = true buttonLike.setBackgroundResource(R.drawable.ic_favorite_gray_24dp) } 1 -> { isInputEmpty = false buttonLike.setBackgroundResource(R.drawable.ic_send_24dp) } } } }) buttonLike.setOnClickListener { if (isInputEmpty) likePost() else sendComment() }

Slide 54

Slide 54 text

class SendLikeButton(context: Context) : Button(context) { private var sendToLike: AnimatedVectorDrawableCompat? = null private var likeToSend: AnimatedVectorDrawableCompat? = null private var showLike: Boolean = false init { showLike = true likeToSend = AnimatedVectorDrawableCompat.create(context, R.drawable.avd_favorite_to_send) sendToLike = AnimatedVectorDrawableCompat.create(context, R.drawable.avd_send_to_favorite) background = likeToSend } fun showLike() { if (!showLike) { morph() } } fun showSend() { if (showLike) { morph() } } } Third approach:

Slide 55

Slide 55 text

class SendLikeButton(context: Context) : Button(context) { private var sendToLike: AnimatedVectorDrawableCompat? = null private var likeToSend: AnimatedVectorDrawableCompat? = null private var showLike: Boolean = false init { showLike = true likeToSend = AnimatedVectorDrawableCompat.create(context, R.drawable.avd_favorite_to_send) sendToLike = AnimatedVectorDrawableCompat.create(context, R.drawable.avd_send_to_favorite) background = likeToSend } fun showLike() { if (!showLike) { morph() } } fun showSend() { if (showLike) { morph() } } } Third approach:

Slide 56

Slide 56 text

class SendLikeButton(context: Context) : Button(context) { private var sendToLike: AnimatedVectorDrawableCompat? = null private var likeToSend: AnimatedVectorDrawableCompat? = null private var showLike: Boolean = false init { showLike = true likeToSend = AnimatedVectorDrawableCompat.create(context, R.drawable.avd_favorite_to_send) sendToLike = AnimatedVectorDrawableCompat.create(context, R.drawable.avd_send_to_favorite) background = likeToSend } fun showLike() { if (!showLike) { morph() } } fun showSend() { if (showLike) { morph() } } } Third approach:

Slide 57

Slide 57 text

class SendLikeButton(context: Context) : Button(context) { private var sendToLike: AnimatedVectorDrawableCompat? = null private var likeToSend: AnimatedVectorDrawableCompat? = null private var showLike: Boolean = false . . . fun showLike() { if (!showLike) { morph() } } fun showSend() { if (showLike) { morph() } } private fun morph() { val drawable = if (showLike) likeToSend else sendToLike background = drawable drawable?.start() showLike = !showLike } }

Slide 58

Slide 58 text

editText.addTextChangedListener(object : TextWatcher { . . . override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { when (count) { 0 -> buttonLike.showLike() 1 -> buttonLike.showSend() } } })

Slide 59

Slide 59 text

buttonLike.setOnClickListener { TODO("????") } editText.addTextChangedListener(object : TextWatcher { . . . override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { when (count) { 0 -> button_like.showLike() 1 -> button_like.showSend() } } })

Slide 60

Slide 60 text

class SendLikeButton(context: Context) : Button(context) { private var sendToLike: AnimatedVectorDrawableCompat? = null private var likeToSend: AnimatedVectorDrawableCompat? = null private var showLike: Boolean = false init { showLike = true . . . } . . . }

Slide 61

Slide 61 text

class SendLikeButton(context: Context) : Button(context) { private var sendToLike: AnimatedVectorDrawableCompat? = null private var likeToSend: AnimatedVectorDrawableCompat? = null var showLike: Boolean = false init { showLike = true . . . } . . . }

Slide 62

Slide 62 text

buttonLike.setOnClickListener { if (buttonLike.showLike) likePost() else sendComment() } editText.addTextChangedListener(object : TextWatcher { . . . override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { when (count) { 0 -> buttonLike.showLike() 1 -> buttonLike.showSend() } } })

Slide 63

Slide 63 text

class SendLikeButton(context: Context) : Button(context) { private var sendToLike: AnimatedVectorDrawableCompat? = null private var likeToSend: AnimatedVectorDrawableCompat? = null var showLike: Boolean = false var isLiked: Boolean = false fun setLikedView(isLiked: Boolean) { this.isLiked = isLiked if (isLiked) { likeToSend = AnimatedVectorDrawableCompat.create(context, R.drawable.avd_gray_favorite_to_send) sendToLike = AnimatedVectorDrawableCompat.create(context, R.drawable.avd_send_to_gray_favorite) } } }

Slide 64

Slide 64 text

override fun onCreate(savedInstanceState: Bundle?) { . . . apiResponseLikeStatus .subscribe({ buttonLike.setLikedView(it) }, {}).addTo(disposable) }

Slide 65

Slide 65 text

override fun onCreate(savedInstanceState: Bundle?) { . . . buttonLike.setOnClickListener { when (buttonLike.isLiked && buttonLike.showLike) { true -> unlikePost() else -> if (buttonLike.showLike) likePost() else sendComment() } } }

Slide 66

Slide 66 text

override fun onCreate(savedInstanceState: Bundle?) { . . . buttonLike.setOnClickListener { when (buttonLike.isLiked && buttonLike.showLike) { true -> unlikePost() else -> if (buttonLike.showLike) likePost() else sendComment() } } }

Slide 67

Slide 67 text

override fun onCreate(savedInstanceState: Bundle?) { . . . buttonLike.setOnClickListener { when (buttonLike.isLiked && buttonLike.showLike) { true -> unlikePost() else -> if (buttonLike.showLike) likePost() else sendComment() } } }

Slide 68

Slide 68 text

override fun onCreate(savedInstanceState: Bundle?) { . . . buttonLike.setOnClickListener { when (buttonLike.isLiked && buttonLike.showLike) { true -> unlikePost() else -> if (buttonLike.showLike) likePost() else sendComment() } } }

Slide 69

Slide 69 text

sealed class ButtonState { object ShowLiked : ButtonState() object ShowUnLiked : ButtonState() object SendComment : ButtonState() } Reactive approach:

Slide 70

Slide 70 text

class SendLikeButton(context: Context) : Button(context) { private val sendToLike by bindAnimation(R.drawable.avd_send_to_favorite) }

Slide 71

Slide 71 text

class SendLikeButton(context: Context) : Button(context) { private val sendToLike by bindAnimation(R.drawable.avd_send_to_favorite) } fun View.bindAnimation(animateRes: Int) = lazy { AnimatedVectorDrawableCompat.create(context, animateRes) }

Slide 72

Slide 72 text

class SendLikeButton(context: Context) : Button(context) { var state by Delegates.observable(ButtonState.ShowUnLiked) { p, oldValue, newValue -> when (newValue) { ButtonState.SendComment -> { when (oldValue) { ButtonState.ShowUnLiked -> morph(unlikeToSend) ButtonState.ShowLiked -> morph(likeToSend) } } . . . } } }

Slide 73

Slide 73 text

class SendLikeButton(context: Context) : Button(context) { var state by Delegates.observable(ButtonState.ShowUnLiked) { p, oldValue, newValue -> when (newValue) { ButtonState.SendComment -> { when (oldValue) { ButtonState.ShowUnLiked -> morph(unlikeToSend) ButtonState.ShowLiked -> morph(likeToSend) } } ButtonState.ShowLiked -> . . . ButtonState.ShowUnLiked -> . . . } }

Slide 74

Slide 74 text

override fun onCreate(savedInstanceState: Bundle?) { . . . RxView.clicks(buttonLike) .subscribe({ when (buttonLike.state) { ButtonState.SendComment -> sendComment() ButtonState.ShowLiked -> unlikePost() ButtonState.ShowUnLiked -> likePost() } }, {}).addTo(disposable) val textChanged = RxTextView.textChanges(editText) Observable.combineLatest(apiResponseLikeStatus, textChanged, BiFunction { t1, t2 -> return@BiFunction when (t2.count() > 0) { true -> ButtonState.SendComment else -> if (t1) ButtonState.ShowLiked else ButtonState.ShowUnLiked } }) .subscribe({ buttonLike.state = it }, {}).addTo(disposable) }

Slide 75

Slide 75 text

override fun onCreate(savedInstanceState: Bundle?) { . . . RxView.clicks(buttonLike) .subscribe({ when (buttonLike.state) { ButtonState.SendComment -> sendComment() ButtonState.ShowLiked -> unlikePost() ButtonState.ShowUnLiked -> likePost() } }, {}).addTo(disposable) val textChanged = RxTextView.textChanges(editText) Observable.combineLatest(apiResponseLikeStatus, textChanged, BiFunction { t1, t2 -> return@BiFunction when (t2.count() > 0) { true -> ButtonState.SendComment else -> if (t1) ButtonState.ShowLiked else ButtonState.ShowUnLiked } }) .subscribe({ buttonLike.state = it }, {}).addTo(disposable) }

Slide 76

Slide 76 text

override fun onCreate(savedInstanceState: Bundle?) { . . . RxView.clicks(buttonLike) .subscribe({ when (buttonLike.state) { ButtonState.SendComment -> sendComment() ButtonState.ShowLiked -> unlikePost() ButtonState.ShowUnLiked -> likePost() } }, {}).addTo(disposable) val textChanged = RxTextView.textChanges(editText) Observable.combineLatest(apiResponseLikeStatus, textChanged, BiFunction { t1, t2 -> return@BiFunction when (t2.count() > 0) { true -> ButtonState.SendComment else -> if (t1) ButtonState.ShowLiked else ButtonState.ShowUnLiked } }) .subscribe({ buttonLike.state = it }, {}).addTo(disposable) }

Slide 77

Slide 77 text

override fun onCreate(savedInstanceState: Bundle?) { . . . RxView.clicks(buttonLike) .subscribe({ when (buttonLike.state) { ButtonState.SendComment -> sendComment() ButtonState.ShowLiked -> unlikePost() ButtonState.ShowUnLiked -> likePost() } }, {}).addTo(disposable) val textChanged = RxTextView.textChanges(editText) Observable.combineLatest(apiResponseLikeStatus, textChanged, BiFunction { t1, t2 -> return@BiFunction when (t2.count() > 0) { true -> ButtonState.SendComment else -> if (t1) ButtonState.ShowLiked else ButtonState.ShowUnLiked } }) .subscribe({ buttonLike.state = it }, {}).addTo(disposable) }

Slide 78

Slide 78 text

class SendLikeButton(context: Context) : Button(context) { var state by Delegates.observable(ButtonState.ShowUnLiked) { p, oldValue, newValue -> when (newValue) { . . . } } private val sendToLike by bindAnimation(R.drawable.avd_send_to_favorite) . . . private val redLike by lazy { R.drawable.ic_favorite_red_24dp } . . . private fun morph(avd: AnimatedVectorDrawableCompat?) { background = avd avd?.start() } } fun View.bindAnimation(animateRes: Int) = lazy { AnimatedVectorDrawableCompat.create(context, animateRes) } sealed class ButtonState { . . . }

Slide 79

Slide 79 text

References: https://kotlinlang.org/docs/reference/delegated-properties.html https://www.manning.com/books/kotlin-in-action https://github.com/AidaIssayeva/Reactive-approach-to-delegation

Slide 80

Slide 80 text

Thank you Aida Issayeva @aida_isay