Reactive Clean Architecture with Android Architecture Components

Lucia Payo
Sep 5, 2017 · 12 min read

What happens if we take reactive, clean architecture and the recently released android architecture components and put all of them together? This is what we did in N26 and the result is working pretty well:

  • There is a clean separation of concerns, making the features easier to navigate in code.
  • Brings a common understanding on how to build features, which makes it easier to review each others’ code.
  • As we apply this architecture, we create more content that can be reused and development becomes faster.
  • Reactive programming brings a higher level of abstraction to the way we code, removing some of the weight from our side.

In this initial post we are going to explain the main ideas behind the architecture and how it works, it is going to be more theoretical than practical, that’s why we’ve decided to create a second post explaining step by step how to apply this architecture in a practical case:

We have also created a small app to showcase the architecture that can be found in this repository:

We realised there are several ways to use RxJava. The most straightforward one is for asynchronous calls, where you subscribe to an Observable that will eventually emit onNext or onError and complete afterwards, a typical example of this are the observables from retrofit calls. It’s like a callback on steroids, you can control the threads where things are executed very easily and there’s a bunch of very powerful operators that allow you to combine/transform the result. This is a great way to use RxJava but there’s more.

In most apps there’s a set of data that is the core information of the app, on which the features are based. For instance, in N26 we have different tabs in the main screen that show credit, insurances, all the movements in the account, etc. All these views show the user’s info that we get from the backend. This data is the heart of the app and everything is based on it.

Image for post
Image for post
N26 insurance feature

What if we could observe this data forever and receive a new version of it every time it changes?

The difference with the previous scenario is that the streams never end, so it is possible to subscribe to them once and keep receiving updates of the observed data until the consumer decides to unsubscribe. In other words, the producer is not in charge of deciding when the stream ends, the consumer will decide when it should stop receiving events by unsubscribing. This is what we mean by reactive and the core idea behind the architecture presented in this series.

Reactive clean architecture

Most of developers are familiar with clean architecture and it helps a lot when it comes to separation of concerns in the app. We followed its guidelines to separate our features in three layers.

Image for post
Image for post
Clean Architecture layers

The responsibility of this layer is to manipulate and coordinate the different sources of data. It has three main components: the repository, the network services and the reactive store .

The main idea is to get the data from the external source through the network services, store this data inside the reactive store, which can be seen as the internal data source, and expose the data through the repository to the upper layer.

Image for post
Image for post
Main components of the data layer

The repository

It is the access point and the interface to the outside. These are the four main operations that are exposed in this layer:

  • Get: Returns an infinite stream that will emit updates of the specified type of data but doesn’t ensure there will be a value inside. The stream is built so it emits right after subscription and emits NONE when the store is empty. This stream is designed to not complete or error.
  • Fetch: It fetches the data from external sources, typically from API, and stores the data once it receives it. This is the action that feeds the “Get”. Returns a Completable that signals whether the fetch has completed or error. Note that there is no onNext event in Completable, this means the fetched data will not return in any way through the fetch method.
  • Request: This is an alternative to fetch operation when we don’t need the infinite stream counterpart. It returns a Single that will return the data in the onNext event and complete or error if there was any problem.
  • Push, Delete: It sends or deletes data to and from external sources. Provides a Single or a Completable depending if there is any data coming back from this operations.

The reactive store

This is the component behind the Get. It holds the data and provides infinite streams to it. Is the most complex part of the whole architecture and must be designed carefully: this layer will be holding the data and will be accessed asynchronously, it must be solid against concurrency.

There are many ways of implementing this component depending on the nature of the features in the app. For the architecture to work, this is how the interface should look like:

public interface ReactiveStore<Key, Value> {    Flowable<Option<Value>> getSingular(@NonNull final Key key);    Flowable<Option<List<Value>>> getAll();    void storeSingular(@NonNull final Value model);    void storeAll(@NonNull final List<Value> modelList);
}
  • Provides two types of “get” methods: the getSingular(id), to get a stream that will emit updates of the singular element with the passed id, and the getAll(), to get a stream that will emit updates when there is changes in any of the elements inside the store.
  • Provides two types of “store” methods: the storeSingular(object) and the storeAll().
  • When storeSingular(object) is called, both the getSingular(id) and the getAll() should emit.
  • When storeAll() is called, getAll() and all the existing getSingular(id) should emit.
  • Both getAll() and getSingular(id) emit right after subscription.
  • Both getAll() and getSingular(id) emit NONE when the store is empty.

The streams are created on request, this way we know when the data is being observed and only update the stream in this case.

Some other operations could be added, such as replaceAll() or clear(), as long as the streams are updated consistently.

Under these component we can coordinate the process of storing data in one or more forms of storage. The simplest would be having a memory cache but it could also be a database, the file system, shared preferences or anything else that could be considered a persistence method. It is also possible to have a combination one or more of these methods, for instance, the combination of memory cache on top of database. The possibilities are many, we just have to coordinate the process of storing the objects and feeding the streams accordingly.

We also want to share with you some of the solutions we came up in N26 for the reactive store. They’ll be coming up soon in different blog posts, stay tuned!

Mapping before storing

The plan is to put in the store the objects that are going to be consumed by the layers on top. This layer will build use cases out of these objects so let’s make sure the data we store is in good conditions.

Most of apps get the data from an API, which has a contract that describes what data and what format this API should serve the data. In 99% of the cases this contract is satisfied but… what happens when it is not? We could spend hours debugging, thinking there is a bug in the app’s code when the actual problem is a missing field in the API response.

What if we design the entry point of the app to check a set of constraints or conditions and if they are not met show an error message describing exactly what is wrong?

This can be achieved by defining two POJOs, one that represents the object that comes from the external source, we refer to this as the “raw” entity, and another one that is the mapped object, the one that has been checked and is safe to store, we call this one the “safe” entity. By mapping the raw entity to the safe entity before storing it we are creating a first line of defence.

Image for post
Image for post
Raw to safe entity mapping

One of the things we do in N26 app is to get rid of the null in our code as much as we can, we use Options instead to represent the possibility of the absence of an object. More precisely we use the Options library from Tomek Polański.

The perfect place to get rid of null is in this mapper, where every parameter can be checked and mapped toOption<Parameter> in the case is fine when is missing, or for the contrary, throw an exception if it is a mandatory parameter for the feature to operate.

This layer sits on top of the data and is responsible for coordinating the actions to the repository. It can also perform some mappings to prepare the objects coming from the data layer, that way the presentation layer can consume them easily. We call the main component in this layer the ReactiveInteractor. As you can see in the snippet below, we’ve defined several types to describe the nature of the different operations.

interface SendInteractor<Params, Result> {

Single<Result> getSingle(Option<Params> params);
}
interface DeleteInteractor<Params, Result> { Single<Result> getSingle(Option<Params> params);
}
interface RetrieveInteractor<Params, Object> {

Flowable<Object> getBehaviorStream(Option<Params> params);
}

interface RefreshInteractor<Params> {

Completable getSingle(Option<Params> params);
}
interface RequestInteractor<Params, Result> {

Single<Result> getSingle(Option<Params> params);
}

All of them define an Option<Params> as input. This way we can pass any input if needed, but also we can pass NONE in the case the specific operation doesn’t require anything.

They return different types of reactive objects depending on the nature of the operation:

  • SendInteractor, DeleteInteractor and RequestInteractor, they all return a Single since the nature of all this operations is to provide a result and complete.
  • RetrieveInteractor returns a Flowable designed to never complete. This one can be seen as the domain version of the data layer’s Get operation, although there are some important differences. The data layer’s Get can be seen as a “pipe” to some form of storage. It doesn’t guarantee there’s going to be an actual value in the store, in which case it will notify this state by emitting NONE as we saw before. RetrieveInteractor is different since it has to guarantee the value. Meaning that it has to do whatever is possible to get this value or error in case it wasn’t possible. This usually means triggering a fetch if the Flowable from the data layer’s Get emits NONE.
  • RefreshInteractor is in charge of performing the necessary actions to update the data layer. This operation will cause the Flowable of the RetrieveInteractor to emit due to the nature of the data layer.

The ReactiveInteractors can be combined and nested. It can be seen as different level of interactors. The lowest level interactor is the one that accesses the repository directly. A higher level interactor could use one or more of this lower level interactors and map the results. The idea is that each interactor does one thing and one thing only, this way is like having pieces that you can put together to build something bigger.

The domain is 100% Java only, meaning that there are no Android framework related objects here.

This is the last layer, responsible for building the objects the views are going to consume and processing the actions performed in this views. This is also the layer where the Android Architecture Components are used, more specifically, the LiveData and the ViewModel.

The pattern for communicating with the views is MVVM. The idea is that the ViewModel provides the view with a LiveData<ViewEntity> object to consume. The view entity should be designed to represent as close as possible the state of one specific view. Some time ago I wrote a blog post about how to design this view entities safely, things that need to be taken into account when dealing with RxJava + MVVM.

In MVVM pattern, the ViewModel is the component that interacts with the views, this leads to often have big view models, especially when the screen is complex. We tried to simplify our view models delegating some responsibilities to other components:

  • Mappers and transformers: They transform the objects coming from the domain layer to view entities.
  • Providers: Sometimes we need something from the framework to build our view entity, for instance, we might need a specific string. We create the StringProvider to abstract the access to this resource.
  • Utilities: There is not much to say here, these are the classical utility classes that contain helper functions.

The View Model should not do any of the things the components above can do. The View Model should coordinate the process of creating view entities and put all the pieces together. After doing this we noticed several benefits: the most obvious one was that the view models have a much smaller size, everything was easier to test since each class has a more specific and narrow responsibility, it is more clear what are the inputs/outputs and what is expected from each class. Of course this also makes the whole presentation layer more understandable and readable.

The importance of a well designed view entity

One common mistake is to pass the view an object that hasn’t been designed for it, usually because we don’t want to create another object that is specific for the view, so we rather just pass the one that comes from our data or domain layer. This should be avoided because it means that the view needs to do some final transformations to be able to consume it, leaving code that contains untested logic.

In our experience, the most critical step working in the presentation layer is the design of the view entities. Creating these POJOs is in a way the main goal of the presentation layer and defines how readable, understandable and testable the whole layer is.

Take your time designing the view entities. You don’t want to deal with a poorly designed one and it is painful to change them when the implementation of the presentation layer is advanced.

Asynchronous nature

When dealing with this type of architecture it is important to have present the asynchronous nature of it. The architecture uses streams extensively, this means we are embracing asynchronous programming. RxJava allows us to do this in a very easy way, is very powerful, but great powers always come with great responsibilities.

The architecture is based on the idea of event based programming: you get events that you can combine, transform or do some other things through the streams, but the events are meant to be transient and immutable, they exist at one point in time inside a function in the chain, until they are transformed into some other object and passed down. So DON’T:

  • Mutate the object in the event, always create a new one. This can be achieved by enforcing immutability in all the objects, which is a general good practice.
  • Store the object from the event in a local/global variable. This object’s scope is in the function and shouldn’t exist outside.

The last point is aiming to eliminate all the unnecessary internal intermediate states we sometimes create thinking they’ll make our lives easier. For instance, a variable in the class to store the latest result of an operation that we want to compare with next time, or an internal flag to know if an operation has already been performed. When we create these type of states we are not in control, we are ignoring the asynchronous nature of our code, but ignoring it doesn’t make it disappear, we are setting up a trap for ourselves, this is the perfect scenario for corrupted states and race conditions. RxJava provides operator with memory so use them, they’ve been designed to work in a reactive asynchronous environment.

This is the end of this first post, if you are ready to see this architecture in action with a practical example check out the follow up post!

Interested in joining one of our teams as we grow?

If you’d like to join us on the journey of building the mobile bank the world loves to use, have a look at some of the Tech roles we’re looking for here.

InsideN26

It takes people of all makeups to create a bank that adapts…

Thanks to Pablo Perez, Florina Muntenescu, and Omkar Pimple

Lucia Payo

Written by

Be functional, be reactive. Android Team Lead @ N26

InsideN26

InsideN26

It takes people of all makeups to create a bank that adapts to the needs of millions. That's why we mix data, intuition, and heart. To build a powerful tool, to make life that much easier. We're some of the best at what we do. And want you to be too.

Lucia Payo

Written by

Be functional, be reactive. Android Team Lead @ N26

InsideN26

InsideN26

It takes people of all makeups to create a bank that adapts to the needs of millions. That's why we mix data, intuition, and heart. To build a powerful tool, to make life that much easier. We're some of the best at what we do. And want you to be too.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store