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 Slide

  2. has a chimney
    has a window
    has a door

    View Slide

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

    View 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 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 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 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 Slide

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

    View Slide

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

    View 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 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 Slide

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

    View 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 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 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 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 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 Slide

  18. 1. lazy

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. 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 Slide

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

    View Slide

  24. 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 Slide

  25. 2. notNull

    View Slide

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

    View Slide

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

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

    View Slide

  29. 3. map

    View Slide

  30. 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 Slide

  31. 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 Slide

  32. 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 Slide

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

    View Slide

  34. 4. observable

    View Slide

  35. 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 Slide

  36. 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 Slide

  37. 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 Slide

  38. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

  43. 5. vetoable

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  47. 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 Slide

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

  49. Delegated properties in custom views

    View Slide

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

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

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

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

  54. 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 Slide

  55. 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 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
    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 Slide

  57. 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 Slide

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

    View Slide

  59. 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 Slide

  60. 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 Slide

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

    View Slide

  62. 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 Slide

  63. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  71. 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 Slide

  72. 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 Slide

  73. 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 Slide

  74. 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 ->
    [email protected] when (t2.count() > 0) {
    true -> ButtonState.SendComment
    else -> if (t1) ButtonState.ShowLiked else
    ButtonState.ShowUnLiked
    }
    })
    .subscribe({
    buttonLike.state = it
    }, {}).addTo(disposable)
    }

    View Slide

  75. 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 ->
    [email protected] when (t2.count() > 0) {
    true -> ButtonState.SendComment
    else -> if (t1) ButtonState.ShowLiked else
    ButtonState.ShowUnLiked
    }
    })
    .subscribe({
    buttonLike.state = it
    }, {}).addTo(disposable)
    }

    View Slide

  76. 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 ->
    [email protected] when (t2.count() > 0) {
    true -> ButtonState.SendComment
    else -> if (t1) ButtonState.ShowLiked else
    ButtonState.ShowUnLiked
    }
    })
    .subscribe({
    buttonLike.state = it
    }, {}).addTo(disposable)
    }

    View Slide

  77. 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 ->
    [email protected] when (t2.count() > 0) {
    true -> ButtonState.SendComment
    else -> if (t1) ButtonState.ShowLiked else
    ButtonState.ShowUnLiked
    }
    })
    .subscribe({
    buttonLike.state = it
    }, {}).addTo(disposable)
    }

    View Slide

  78. 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 Slide

  79. 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 Slide

  80. Thank you
    Aida Issayeva
    @aida_isay

    View Slide