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

Droidcon SF: Reactive approach to delegation

Droidcon SF: Reactive approach to delegation

PRESENTED AT:
Droidcon SF
https://www.sf.droidcon.com/speaker/Aida-Issayeva

DATE:
November 25, 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 25, 2019
Tweet

More Decks by Aida Issayeva

Other Decks in Programming

Transcript

  1. Reactive approach to
    delegation in Kotlin
    Aida Issayeva
    @aida_isay

    View full-size slide

  2. has a chimney
    has a window
    has a door

    View full-size slide

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

    View full-size slide

  4. 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() { }
    }

    View full-size slide

  5. 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();
    }
    }

    View full-size slide

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

    View full-size slide

  7. “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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  10. 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();
    }
    }

    View full-size slide

  11. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  14. //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

    View full-size slide

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

    View full-size slide

  16. //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

    View full-size slide

  17. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  21. 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)
    }
    }

    View full-size slide

  22. init
    val
    init
    val
    init
    val
    ?
    LazyThreadSafetyMode
    SYNCHRONIZED PUBLICATION NONE

    View full-size slide

  23. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  27. 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
    }

    View full-size slide

  28. 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
    }

    View full-size slide

  29. 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)
    }

    View full-size slide

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

    View full-size slide

  31. 4. observable

    View full-size slide

  32. 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)
    }
    }
    }

    View full-size slide

  33. 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)
    }
    }
    }

    View full-size slide

  34. 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)
    }
    }
    }

    View full-size slide

  35. 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)
    }
    }
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  39. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  42. 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
    }
    }

    View full-size slide

  43. 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")
    }
    }

    View full-size slide

  44. 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

    View full-size slide

  45. Delegated properties in custom views

    View full-size slide

  46. 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:

    View full-size slide

  47. 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
    }
    }
    }
    })
    }

    View full-size slide

  48. 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:

    View full-size slide

  49. 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()
    }

    View full-size slide

  50. 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:

    View full-size slide

  51. 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:

    View full-size slide

  52. 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:

    View full-size slide

  53. 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
    }
    }

    View full-size slide

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

    View full-size slide

  55. 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()
    }
    }
    })

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  58. 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()
    }
    }
    })

    View full-size slide

  59. 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)
    }
    }
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  67. 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)
    }

    View full-size slide

  68. 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)
    }
    }
    . . .
    }
    }
    }

    View full-size slide

  69. 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 -> . . .
    }
    }

    View full-size slide

  70. 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)
    }

    View full-size slide

  71. 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)
    }

    View full-size slide

  72. 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)
    }

    View full-size slide

  73. 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)
    }

    View full-size slide

  74. 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 {
    . . .
    }

    View full-size slide

  75. 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

    View full-size slide

  76. Thank you
    Aida Issayeva
    @aida_isay

    View full-size slide