Build a system of reactive components using Kotlin
3r3675.
3r3675.
Hello! My name is Anatoly Varivonchik, I'm an Android developer for Badoo. Today I will share with you the translation of the second part of the article by my colleague Zsolt Kocsi on the implementation of MVI, which we use daily in the development process. The first part is 3r339. here
. 3r3675.
3r3675.
3r3661. What we want and how we will do it 3r3662. 3r3675.
In the first part of the article, we met 3r3r6767. Features [/b] , the central elements of 3r3777. MVICore
that can be reused. They may have as simple a structure as possible and include only one Reducer 3r3673. , and can become a full-featured tool for managing asynchronous tasks, events and much more. 3r3675.
3r3675.
Each Feature is monitored - it is possible to subscribe to changes in its state and receive notifications about it. In this feature, you can subscribe to the input source. And this makes sense, because with the inclusion of Rx in the code base, we already have a lot of observable objects and subscriptions at various levels. 3r3675.
3r3675.
It is precisely in connection with the increase in the number of reactive components that it is time to reflect on what we have and whether it is possible to make the system even better. 3r3675.
3r3335. 3r3678. 3r3675.
We have to answer three questions: 3r33675.
3r3675.
3r33525.
What elements should be used when adding new reactive components? 3r3675.
3r3638.
What is the easiest way to manage subscriptions? 3r3675.
3r3638.
Is it possible to abstract from lifecycle management /the need to clear subscriptions to avoid memory leaks? In other words, can we separate the binding of components from subscription management? 3r3675.
3r3638.
3r? 3577. 3r3675.
In this part of the article, we will look at the basics and advantages of building a system using reactive components and see how Kotlin helps in this. 3r3675.
3r3675.
3r3661. The main elements of 3r3662. 3r3675.
By the time we come to work on the design and standardization of our 3r367671. Features , we have already tried many different approaches and decided that Features will be made in the form of reactive components. At first we focused on the main interfaces. First of all, we had to decide on the types of input and output data. 3r3675.
3r3675.
We reasoned as follows:
3r3675.
3r3611.
We will not reinvent the wheel - let's see which interfaces already exist. 3r3675.
3r3638.
Since we are already using the RxJava library, it makes sense to refer to its basic interfaces. 3r3675.
3r3638.
The number of interfaces should be kept to a minimum. 3r3675.
3r3638.
3r3640. 3r3675.
As a result, we decided to use ObservableSource <Т> 3r3673. for output and 3r3r6767. Consumer <Т> 3r3673. for input. Why not [b] Observable /Observer , you ask. Observable - abstract class, from which you need to be inherited, and ObservableSource - the interface you implement that fully satisfies the need for the implementation of the reactive protocol. 3r3675.
3r3675. package io.reactivex;
import io.reactivex.annotations. *;
/**
* Represents a basic, non-backpressured {@link Observable} source base interface,
* consumable via an {@link Observer}.
*
* @param
the element type
* @ since ???r3r3685. * /
public interface ObservableSource
{
/**
* Subscribes the Observer to this ObservableSource instance.
* @param observer the Observer, not null
* @throws NullPointerException if {@code observer} is null
* /
void subscribe (@NonNull Observer <? super T> observer);
}
3r3653. 3r3654. 3r3675.
Observer The first interface that comes to mind implements four methods: onSubscribe, onNext, onerror, and onComplete. In an effort to simplify the protocol as much as possible, we preferred it to Consumer <Т> 3r3673. which accepts new elements using a single method. If we chose [b] Observer , the remaining methods would most often be redundant or would work differently (for example, we would like to present errors as part of the state (3r36771. State 3r3672.), and not as an exception, and certainly not interrupt the flow). 3r3675.
3r3675. /**
3r3654. 3r3675.
* A functional interface (callback) that accepts a single value.
* @param
the value type
* /
public interface Consumer
{
/**
* Consume the given value.
* @param t the value
* @throws Exception on error
* /
void accept (T t) throws Exception;
}
So, we have two interfaces, each of which contains one method. Now we can link them by signing Consumer <Т> 3r3673. on 3r3671. ObservableSource <Т> 3r3673. . The latter only accepts instances of [b] Observer <Т> 3r3673. , but we can wrap it in [b] Observable <Т> 3r3673. which is subscribed to [b] Consumer <Т> 3r3673. : 3r3675.
3r3675. val output: ObservableSource
3r3654. 3r3675.
= Observable.just ("item1", "item2", "item3")
val input: Consumer
= Consumer {System.out.println (it)}
val disposable = Observable.wrap (output) .subscribe (input)
(Fortunately, the function .Wrap (output) Does not create a new object if Output Is already [b] Observable 3r33215. 3r36722.). 3r3675.
3r3675.
You may remember that component [b] Feature from the first part of the article I used input data of type Wish (corresponds to the Intent from the Model-View-Intent) and the output data is State , and therefore it can be on both sides of the ligament: 3r3675.
3r3675. //Wishes -> Feature
3r3654. 3r3675.
val wishes: ObservableSource
= Observable.just (Wish.SomeWish)
val feature: Consumer
= SomeFeature ()
val disposable = Observable.wrap (wishes) .subscribe (feature)
//Feature -> State consumer
val feature: ObservableSource
= SomeFeature ()
val logger: Consumer
= Consumer {System.out.println (it)}
val disposable = Observable.wrap (feature) .subscribe (logger)
This binding 3r33434. Consumer 3r33535. and 3r?334. Producer 3r33535. already looks simple enough, but there is an even easier way in which you do not need to either create subscriptions manually or cancel them. 3r3675.
3r3675.
Introducing [b] Binder 3r3673. . 3r3675.
3r3675.
3r3661. Binding "on steroids"
3r3675.
3r3777. MVICore
contains a class called [b] Binder 3r3673. which provides a simple API for managing Rx subscriptions and has a number of cool features. 3r3675.
3r3675.
Why is it needed? 3r3675.
3r3675.
3r3611.
Creating a binding by subscribing input data for the weekend. 3r3675.
3r3638.
Ability to unsubscribe at the end of the life cycle (when it is an abstract concept and has no relation to Android). 3r3675.
3r3638.
Bonus: [b] Binder 3r3673. allows you to add intermediate objects, for example, for logging or time-travel-debugging. 3r3675.
3r3638.
3r3640. 3r3675.
Instead of manually subscribing, you can rewrite the examples above as follows: 3r336755.
3r3675. val binder = Binder ()
3r3654. 3r3675.
binder.bind (wishes to feature)
binder.bind (feature to logger)
Thanks to Kotlin, everything looks very simple. 3r3675.
3r3675.
These examples work if the input and output types are the same. But what if it is not? By implementing the expansion function, we can make the transformation automatic:
3r3675. val output: ObservableSource GitHub .
It may be interesting
weber
Author21-11-2018, 05:18
Publication DateDevelopment / Programming
Category- Comments: 1
- Views: 360