Clean Architecture – Reactive Approach

Clean Architecture – Reactive Approach

Clean Architecture can be approached in several ways. One solution is to use RxJava 2, and in this post we will take a close look at this approach. In the presentation layer we will use the proven standard Model View Presenter, in the domain layer we will have UseCases with a single responsibility, and in the data layer we will apply Repository Pattern. All of this will be controlled with RxJava streams.

Repository Pattern

Suppose that we have a presenter that gets us users and sets their data by calling the appropriate methods on the view. The presenter should not know where the data is from, therefore, first, let’s create UsersRepository, which will use the database and REST services.

In the following examples we also used Dagger2. It is not necessary, however it makes implementation much easier.

@Singleton
public class UsersRepository {

    public static final int DEFAULT_AMOUNT = 5;
    private UsersService usersService;
    private DatabaseManager databaseManager;

    @Inject
    public UsersRepository(UsersService usersService, DatabaseManager databaseManager) {
        this.usersService = usersService;
        this.databaseManager = databaseManager;
    }

    public Flowable<User> loadUsers(String gender) {
        Flowable<User> remoteUsers = usersService.getUsersList(DEFAULT_AMOUNT).flatMap(Flowable::fromIterable);
        Flowable<User> localUsers = databaseManager.getUsers();
        return remoteUsers.concatWith(localUsers).filter(user -> user.getGender().equals(gender));
    }
}

Let’s assume here that we are dealing with simplified logic, as if getting users from the repository provided the items from both the server and the database. We can do this by concatenating 2 streams from UsersService and DatabaseManager, objects injected to the repository using Dagger.

They both return the data using Flowable, however, the service provides us with a Flowable<List<User>>, and the database with Flowable<User>. Thanks to the operator fromIterable we process stream of a list to stream of items and concatenate both streams through concatWith. In addition, the method loadUsers accepts the argument of sex, which it uses to filter the users.

Use Case

We already have a finished repository, but the presenter should not use it directly. Into presenters we inject primarily UseCases, which can use multiple repositories. UseCases define all the actions that can be performed in the application and each of them has a single responsibility. So let’s create a GetUsersUseCase that gets users from the repository. Obviously, naming depends on convention, this is just the one I chose.

public class GetUsersUseCase extends SingleUseCase<List<User>> {

    private UsersRepository userRepository;
    private String gender;

    @Inject
    public GetUsersUseCase(UsersRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    protected Single<List<User>> buildUseCaseSingle() {
        return userRepository.loadUsers(gender).toList();
    }
}

As you can see, we can distinguish the method buildUseCaseSingle(), which we will override from the superior class – SingleUseCase. We will discuss its content in a moment, but for now the most important is that inside buildUseCaseSingle() method we return a stream that will be of interest to the presenter. In this case, it is a Single containing the list of users. If we want to call the method from the repository with selected parameters, we simply create an additional field and setter.

Single Use Case

public abstract class SingleUseCase<T> extends UseCase {

    protected abstract Single<T> buildUseCaseSingle();

    private Single<T> buildUseCaseSingleOnIo() {
        return buildUseCaseSingle().compose(RxUtil.applySingleIoSchedulers());
    }

    public void execute(final Consumer<? super T> onSuccess) {
        disposable = buildUseCaseSingleOnIo().subscribe(onSuccess);
    }

    public void execute(final Consumer<? super T> onSuccess, final Consumer<Throwable> onError) {
        disposable = buildUseCaseSingleOnIo().subscribe(onSuccess, onError);
    }
}

SingleUseCase is a base UseCase returning Single of the chosen type. Single is simply a stream returning only one item, so after subscribing to it, we can handle onSuccess or onError. In this order we create the method execute in 2 versions: when we do not intend to handle the error (it is not advised to use), and for the case when we want to handle the error.

As the method buildUseCaseSingle() is abstract, and we have just implemented it in GetUsersUseCase, we should use it at this point. First, we set the appropriate Schedulers, here we are using compose and own method of util class. In short, it boils down to the use of operators subscribeOn(Schedulers.io()) and observeOn(AndroidSchedulers.mainThread()), so that everything will be downloaded asynchronously in the background, and at the end we can use relevant data in the main thread.

public static <T> SingleTransformer<T, T> applySingleIoSchedulers() {
      return single -> single.subscribeOn(Schedulers.io())
              .observeOn(AndroidSchedulers.mainThread());
}

Therefore in the execute methods we subscribe to already implemented method and assign the result to object disposable, which is located in the abstract base class UseCase:

public abstract class UseCase {

    protected Disposable disposable = Disposables.disposed();

    public void dispose() {
        disposable.dispose();
    }
}

We do this in order to be able to stop the whole stream at any moment we need to. We can do it with the method dispose(), which the presenter will call on UseCase when it will be to be permanently destroyed or so. Similarly, we can also create FlowableUseCase or CompletableUseCase.

Presenter

At the end it all comes down to injecting UseCase to the presenter and calling:

getUsersUseCase.setGender(gender);
getUsersUseCase.execute(this::consumeUsers, this::handleError);

We implement methods consumeUsers and handleError in which we can work on the view. And when we destroy our view and disconnect the presenter permanently, we just need to call:

getUsersUseCase.dispose();

We can do this in many ways, for example after injecting a few UseCases to the presenter save them to the list and, if necessary, use:

Flowable.fromIterable(useCases).subscribe(UseCase::dispose);

I intentionally do not show the full implementation of the presenter, as there are many methods of doing MVP, and Clean Architecture can be used in virtually any of them. It is enough that the presenter uses appropriate UseCases.

 

I know that this is just an introduction, which is why I show the most important elements of architecture, so as not to hide the idea. Clean Architecture approach can be implemented in many ways, this is just one of them. Thanks to it, we can obtain a coherent, testable application architecture in a simple way.

 

Learn more

Static Shortcuts in Android 7.1 Nougat

With the new Android version, Nougat 7.1 (API 25), Google gives us some interesting possibilities. One of them is the option to create shortcuts to an application. In this post I will show you how static shortcuts work.

Read more

Bitrise Tests Made Easier: Update JIRA Issues with Build Number and Forget About Delays in QA Testing

When you trigger Bitrise build with changes and forget to tell QA specialists some essential information, there are two likely scenarios. You waste time waiting for the update from the tester, only you don’t know it’s never coming. Or you get so many questions about builds that you can’t keep up. Sounds familiar? If so, let us tell you about the JIRA issue update that keeps workflow in order.

Read more

WebSockets on iOS – Real-time Communication That Doesn’t Slow Down the App

When you want to download data for the app, you probably use the API RESTful interface. All it takes is to ask the server for the data and that’s it! This method works well when the app doesn’t need permanent access to new information. But what to do if the content has to be updated in real-time? A delay can make it impossible for the user to buy an item or make a sports bet, for example. But there’s a solution – WebSockets on iOS. Check out why you should implement them, and how to do it.

Read more

Project estimation

Let us know what product you want to build and how we can help you.

Why choose us?

Logo Mobile Trends Awards

Mobile Trends Awards 2017

Nomination in
M-COMMERCE category

Logo Legalni bukmacherzy

Legal Bookmakers Award 2019

Best Mobile App

Mobile Trends Awards logo

Mobile Trends Awards 2020

Nomination in SPORTS & RECREATION category

18

client reviews

Clutch logo