External Events in Knot

July 7, 2020 | 5 min read
  • #  knot
  • #  android
  • #  kotlin
  • Introduction

    In this post you will learn how to observe and handle external Events in Knot. If you are new to Knot library, it would make sense to get to know its basic concepts before you continue reading.

    💦 Event Source

    Each knot instance has the ability to subscribe for external events and turn them into changes for further processing in the reducer. This feature is useful for observing changes in database, user location updates or other external triggers.

    Events can be delivered to Knot using an instance of the Observable type, which I will refer to as an event source from here on. Events-section is the place where the knot instance subscribes to the event sources and maps events into changes.

    val userLocation: Observable<Location>
    
    knot<State, Change, Action> {
        events {
            source {
                userLocation.map { Change.UpdateLocation(it.lat, it.lng) }
            }
        }
        changes {
            reduce { change ->
                when(change) {
                    Change.UpdateLocation -> handle...
                }
            }
        }
    }
    

    In the code snippet above each Location event is turned into an Change.UpdateLocation. Knot takes the change and delivers it to the reducer, where the actual event-handling logic resides.

    Lifecycle

    A knot instance subscribes to its event sources right after the instance is created and stays subscribed to the event sources until the instance is disposed. If a CompositeKnot is used, the knot instance subscribes to its event sources right after compose() method was called.

    In this respect, the knot instance and its event sources share the same lifecycle.

    Initialization Events

    The fact that events sources are subscribed when their knot instance is created can be used for emitting a one-time initialization event.

    In the code snippet below, the Change.Initialize is emitted just once, right after the knot instance is created. Reducer can implement whatever initialization logic is needed like loading initial data, for example.

    knot<State, Change, Action> {
        events {
            source {
                Observable.just(Change.Initialize)
            }
        }
        changes {
            reduce { change ->
                when(change) {
                    Change.Initialize -> initialize...
                }
            }
        }
    }
    

    🧊 Cold Event Source

    A continuous subscription described above can be expensive when it is applied to a resource- or power-critical event source.

    For example, a continuous subscription to the user location event source on mobile devices can increase power consumption. If we have a view, which is bound to the knot instance with such subscription, and the view becomes invisible, then the knot instance will continue receiving user location updates and ⚡️consuming power in background.

    We have to fix this. Our knot instance should

    Fortunately, Knot supports this kind of scenarios. It has a special event source called coldSource 🥶 which subscribes to the underlying observable when the first observer of knot.state subscribes and unsubscribed from the underlying observable when the last observer of knot.state unsubscribes. It is somehow similar to how RefCount operator works.

    Let’s make the user location’s observer example power consumption aware. We just need to replace source 💦 with coldSource 🧊 and keep the rest of the implementation unchanged.

    knot<State, Change, Action> {
      events {
    --  source {
    ++  coldSource {
                userLocation.map { Change.UpdateLocation(it.lat, it.lng) }
            }
        }
    }
    

    Now, when the view becomes invisible and unsubscribes from the State, the knot instance will unsubscribe from the user location event source as well. When the view becomes visible again and subscribes to the State, the knot instance will re-subscribe to the event source anew.

    Here are some of the scenarios where usage of coldSource is rather desirable:

    Refresh Events

    Similar to how we provided Observable.just() to source for emitting initialization events, we can provide it to coldSource for emitting so-called refresh events. As you already know, those events will be emitted each time the first observer subscribes to the State.

    knot<State, Change, Action> {
        events {
            couldSource {
                Observable.just(Change.Refresh)
            }
        }
        changes {
            reduce { change ->
                when(change) {
                    Change.Refresh -> refresh...
                }
            }
        }
    }
    

    We can use this event for fetching fresh data from the server and update the view each time the user switches to our app and the view becomes visible.

    Wrap-up

    I hope you find external Events in Knot useful and this post gives you an idea of when and how to use them in you apps. Have fun and happy coding! ✌️