Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DevFest: Reactive approach to delegation

DevFest: Reactive approach to delegation

PRESENTED AT:
GDG Sydney DevFest

https://sydney-devfest-2019.web.app/speakers/aida_issayeva

DATE:
November 16, 2019

DESCRIPTION:
This talk about delegation and delegated properties in Kotlin and how to incorporate them into your custom views and to make them react immediately to new data. It showcases the usage of delegated properties in custom views
Repo to code sample: https://github.com/AidaIssayeva/Reactive-approach-to-delegation

MORE TALKS & ARTICLES FROM ME: https://cupsofcode.com/talks/

Aida Issayeva

November 16, 2019
Tweet

More Decks by Aida Issayeva

Other Decks in Programming

Transcript

  1. 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() { } }
  2. 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(); } }
  3. Delegation in Java class LivingRoom implements WindowActionListener { private Window

    window = new Window(); @Override public void openWindow() { window.openWindow(); } @Override public void closeWindow() { window.closeWindow(); } }
  4. “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
  5. Delegation in Kotlin class Room : WindowActionListener by Window(), DoorActionListener

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

    by Door() class LivingRoom : WindowActionListener by Window()
  7. 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(); } }
  8. 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
  9. Delegated properties //implement interfaces //for val class UserNameDelegate : ReadOnlyProperty<User,

    String> { override fun getValue(thisRef: User, property: KProperty<*>): String { return "${thisRef.firstName}_${thisRef.lastName}" } }
  10. Delegated properties //implement interfaces //for var class UserNameDelegate : ReadWriteProperty<User,

    String> { 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.") } }
  11. Delegated properties //or use operator //for val class UserNameDelegate {

    operator fun getValue(thisRef: User?, property: KProperty<*>): String { return "${thisRef?.firstName}_${thisRef?.lastName}" } }
  12. Delegated properties //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.") } }
  13. 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
  14. 1. lazy delegate class AboutMeFragment : Fragment() { val component

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

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

    lazy { val appComponent = DaggerHelper.getAppComponent(this) AboutMeComponent.builder() .appComponent(appComponent) .build() } }
  17. 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) } }
  18. 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
  19. notNull delegate class AboutMeFragment : Fragment() { var age: Int

    by Delegates.notNull() } import kotlin.properties.Delegates.notNull class AboutMeFragment : Fragment() { var age: Int by notNull() }
  20. 3. map delegate class Profile(map: Map<String, Any?>) { val firstName:

    String by map val lastName: String by map val phoneNumber: String by map val income: Double by map }
  21. map delegate class Profile(map: Map<String, Any?>) { val firstName: String

    by map val lastName: String by map val phoneNumber: String by map val income: Double by map }
  22. 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) }
  23. map •Simplify access • Define expected structure • But have

    a power to access unexpected fields •Map key == property name
  24. 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("Android", "Summit", "[email protected]") .subscribe { isLoading = false } .addTo(disposables) } } }
  25. 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("Android", "Summit", "[email protected]") .subscribe { isLoading = false } .addTo(disposables) } } }
  26. 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("Android", "Summit", "[email protected]") .subscribe { isLoading = false } .addTo(disposables) } } }
  27. 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("Android", "Summit", "[email protected]") .subscribe { isLoading = false } .addTo(disposables) } } }
  28. Adapters in Java public class NameAdapter extends RecyclerView.Adapter<ViewHolder> { public

    void updateNames(ArrayList<String> names){ this.list = names; notifyDataSetChanged(); } }
  29. Adapters in Kotlin class NameAdapter(val context: Context) : RecyclerView.Adapter<ViewHolder>() {

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

    var list by observable(mutableListOf<String>()) { _, old, new -> if (old != new) notifyDataSetChanged() } }
  31. 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
  32. Adapters in Java public class Adapter extends RecyclerView.Adapter<ViewHolder> { public

    void updateNames(ArrayList<String> names){ if(names.size() <= 5) { this.list = names; notifyDataSetChanged(); } } }
  33. Adapters in Kotlin class NameAdapter(val context: Context) : RecyclerView.Adapter<ViewHolder>() {

    var list by Delegates.vetoable<List<String>>(mutableListOf()) { _, old, new -> new.size <= 5 } }
  34. Vetoable delegate class NameAdapter(val context: Context) : RecyclerView.Adapter<ViewHolder>() { var

    list by Delegates.vetoable<List<String>>(mutableListOf()) { _, old, new -> if (new.size <= 5) { notifyDataSetChanged() return@vetoable true } return@vetoable false } }
  35. 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") } }
  36. 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
  37. 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:
  38. 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 } } } }) }
  39. 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:
  40. 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() }
  41. 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:
  42. 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:
  43. 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:
  44. 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 } }
  45. editText.addTextChangedListener(object : TextWatcher { . . . override fun onTextChanged(s:

    CharSequence?, start: Int, before: Int, count: Int) { when (count) { 0 -> buttonLike.showLike() 1 -> buttonLike.showSend() } } })
  46. 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() } } })
  47. class SendLikeButton(context: Context) : Button(context) { private var sendToLike: AnimatedVectorDrawableCompat?

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

    = null private var likeToSend: AnimatedVectorDrawableCompat? = null var showLike: Boolean = false init { showLike = true . . . } . . . }
  49. 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() } } })
  50. 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) } } }
  51. override fun onCreate(savedInstanceState: Bundle?) { . . . buttonLike.setOnClickListener {

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

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

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

    when (buttonLike.isLiked && buttonLike.showLike) { true -> unlikePost() else -> if (buttonLike.showLike) likePost() else sendComment() } } }
  55. sealed class ButtonState { object ShowLiked : ButtonState() object ShowUnLiked

    : ButtonState() object SendComment : ButtonState() } Reactive approach:
  56. 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) }
  57. class SendLikeButton(context: Context) : Button(context) { var state by Delegates.observable<ButtonState>(ButtonState.ShowUnLiked)

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

    { p, oldValue, newValue -> when (newValue) { ButtonState.SendComment -> { when (oldValue) { ButtonState.ShowUnLiked -> morph(unlikeToSend) ButtonState.ShowLiked -> morph(likeToSend) } } ButtonState.ShowLiked -> . . . ButtonState.ShowUnLiked -> . . . } }
  59. 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<Boolean, CharSequence, ButtonState> { 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) }
  60. 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<Boolean, CharSequence, ButtonState> { 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) }
  61. 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<Boolean, CharSequence, ButtonState> { 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) }
  62. 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<Boolean, CharSequence, ButtonState> { 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) }
  63. class SendLikeButton(context: Context) : Button(context) { var state by Delegates.observable<ButtonState>(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 { . . . }