Android VIPER jet-powered

  • Tutorial


The more lines of code written, the less often you want to duplicate the code, and the more projects are implemented, the more often you go around the old, albeit often loved, rake, and you become more and more interested in architectural solutions.

I think it’s not quite habitual to meet the VIPER architectural template , dearly loved by iOS developers, near Android , we also for the first time passed ears from the neighboring department about this, until we suddenly found out that we began to use such a template in our Android applications.

How could this happen? Yes, very simple. The search for elegant architectural solutions began long before Android applications, and one of my favorite and irreplaceable rules has always been - dividing the project into three loosely coupled layers: Data, Domain, Presentation. And now, once again exploring the Internet for new trends in architectural patterns for Android applications, I came across a great solution: Android Clean Architecture , here, in my humble opinion, everything was fine: splitting into your favorite layers, Dependency Injection, implementation presentation layer as MVP, a familiar and often used for data layer Repository template.

But in addition to long-beloved and familiar techniques in designing, there was a place for discoveries. It was this project that introduced me to the concept of Interactor (an object containing business logic for working with one or more entities), and it was also here that the power of reactive programming was revealed to me.

Reactive programming, and in particular rxJava, is a fairly popular topic of reports and articles over the past year, so you can easily get acquainted with this technology (unless of course you are not already familiar with it), and we will continue the story about VIPER.

Acquaintance with Android Clean Architecture led to the fact that any new project, as well as refactoring of existing ones, boiled down to three layers, rxJava and MVP, and as a domain layer Interactors began to be used. The question of the correct implementation of transitions between screens remained open, and here the concept of Router began to sound more and more often. At first, Router was lonely and lived in the main Activity, but then new screens appeared in the application and Router became very cumbersome, and then another Activity appeared with its Fragments, and then I had to think about navigation seriously. All the basic logic, including switching between screens, is contained in the Presenter; accordingly, the Presenter needs to know about the Router, which in turn must have access to the Activity to switch between the screens, so the Router must have its own and be transmitted to each Activity Presenter at creation.

And once again, looking at the project, it came to understand that we had VIPER - View, Interactor, Presenter, Entity and Router.

I think you noticed on the Observable diagram, that is where all the power of jet propulsion is hidden. The data layer does not just retrieve data from a remote or local storage in the representation that we need, it passes to Interactor the entire sequence of actions wrapped in an Observable, which in turn can continue this sequence at its discretion based on the task being implemented.

Now let’s take a look at a small example of VIPER implementation for Android ( source ):
Suppose that we are faced with the task of developing an application that, every three seconds, requests a list of messages from a “not very flexible” server and displays the latter for each sender, as well as notifies the user of new ones. By tap on the last message, a list of all messages for the selected sender appears, but messages still continue to synchronize with the server every 3 seconds. Also, from the main screen, we can get into the contact list, and view all messages for one of them.

So, let's get started, we have three screens: chats (last messages from each contact), a list of messages from a specific contact, and a list of contacts. We throw the class diagram:



Screens are fragments, transitions between which are regulated by an Activity that implements the Router interface. Each fragment has its own Presenter and implements the interface necessary for interacting with it. To facilitate the creation of a new Presenter and fragment, basic abstract classes were created: BasePresenter and BaseFragment.

BasePresenter - contains links to the View and Router interface, and also has two abstract methods onStart and onStop, repeating the life cycle of the fragment.

public abstract class BasePresenter {
   private View view;
   private Router router;
   public abstract void onStart();
   public abstract void onStop();
   public View getView() {
       return view;
   }
   public void setView(View view) {
       this.view = view;
   }
   public Router getRouter() {
       return router;
   }
   public void setRouter(Router router) {
       this.router = router;
   }
}


BaseFragment - performs the main work with BasePresenter: initializes and passes the interaction interface to onActivityCreated, calls onStart and onStop in the corresponding methods.

Any Android application starts with Activity, we will have only one MainAcivity in which fragments are switched.



As mentioned above, Router lives in an Activity, in a specific example, MainActivity simply implements its interface, respectively, for each Activity its own Router, which controls navigation between fragments inside it, therefore each fragment in such an Activity must have Presenter using the same Router : this is how BaseMainPresenter came about, which every Presenter working in MainActivity should inherit from.

public abstract class BaseMainPresenter 
                                                          extends BasePresenter {
}


When changing fragments in MainActivity, the state of the Toolbar and FloatingActionButton changes, so each fragment should be able to report the necessary state parameters to it in Activity. To implement such an interaction interface, BaseMainFragment is used:

public abstract class BaseMainFragment extends BaseFragment implements BaseMainView {
 public abstract String getTitle(); //заголовок в Toolbar
  @DrawableRes
 public abstract int getFabButtonIcon();  //иконка FloatingActionButton
//событие по клику на FloatingActionButton
 public abstract View.OnClickListener getFabButtonAction(); 
@Override
public void onActivityCreated(Bundle savedInstanceState) {
   super.onActivityCreated(savedInstanceState);
   MainActivity mainActivity = (MainActivity) getActivity();
//Передаем презентеру роутер
   getPresenter().setRouter(mainActivity);
//Сообщаем MainActivity что необходимо обновить Toolbar и FloatingActionButton
   mainActivity.resolveToolbar(this);
   mainActivity.resolveFab(this);
}
@Override
public void onDestroyView() {
   super.onDestroyView();
//Очищаем роутер у презентера
   getPresenter().setRouter(null);
}
   ….
}


BaseMainView is another basic entity for creating fragments in MainActivity; this is the interaction interface that every Presenter in MainActivity knows about. BaseMainView allows you to display an error message and display alerts; this interface implements BaseMainFragment:

...
@Override
public void showError(@StringRes int message) {
   Toast.makeText(getContext(), getString(message), Toast.LENGTH_LONG).show();
}
@Override
public void showNewMessagesNotification() {
   Snackbar.make(getView(), R.string.new_message_comming, Snackbar.LENGTH_LONG).show();
}
...


Having preparations in the form of such base classes, the process of creating new fragments for MainActivity is significantly accelerated.

Router
And here is the MainRouter:


public interface MainRouter {
   void showMessages(Contact contact);
   void openContacts();
}


Interactor
Each Presenter uses one or more Interactor to work with data. Interactor has only two public methods, execute and unsubscribe, that is, Interactor can be executed and unsubscribed from the running process:

public abstract class Interactor {
   private final CompositeSubscription subscription = new CompositeSubscription();
   protected final Scheduler jobScheduler;
   private final Scheduler uiScheduler;
   public Interactor(Scheduler jobScheduler, Scheduler uiScheduler) {
       this.jobScheduler = jobScheduler;
       this.uiScheduler = uiScheduler;
   }
   protected abstract Observable buildObservable(ParameterType parameter);
   public void execute(ParameterType parameter, Subscriber subscriber) {
       subscription.add(buildObservable(parameter)
               .subscribeOn(jobScheduler)
               .observeOn(uiScheduler)
               .subscribe(subscriber));
   }
   public void unsubscribe() {
           subscription.clear();
   }
}


Entity
To access data Interactor uses one or more DataProvider and generates rx.Observable for subsequent execution.

The problem statement for this example included the need for a periodic request to the server, which was easily implemented using RX:

public static long PERIOD_UPDATE_IN_SECOND = 3;
@Override
public Observable> getAllMessages(Scheduler scheduler) {
   return Observable
                        .interval(0, PERIOD_UPDATE_IN_SECOND, TimeUnit.SECONDS, scheduler)
                        .flatMap(this::getMessages);
}


The above code example performs a request for a list of messages every three seconds and sends an alert to the subscriber.

Conclusion
Architecture is the skeleton of an application, and if you forget about it, you can end up with a freak. A clear division of responsibilities between layers and types of classes facilitates support, testing, introducing a new person to the project takes less time and sets up a uniform style in programming. Base classes help to avoid code duplication, and rx does not think about asynchrony. Ideal architecture as well as ideal code are practically unattainable, but to strive for them means to grow professionally.

PS There are ideas to continue the series of articles, telling more about interesting cases in the implementation:
presentation layer - state preservation in a fragment, composite view;
domain layer - Interactor for multiple subscribers;
data layer - organization of caching.
If interested, put a plus sign :)

Also popular now: