📜 ⬆️ ⬇️

Android VIPER on jet propulsion



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 get around the old, though often favorite, rakes, and you become more and more interested in architectural solutions.

I think it’s not unusual for Android to meet an architectural template VIPER , beloved by iOS developers, we also missed the conversation from a neighboring department about it at first, until we suddenly found that we could not help using 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 - the division of the project into three loosely coupled layers: Data, Domain, Presentation. And now, once again studying the Internet for new trends in architectural templates for Android applications, I came across a great solution: Android Clean Architecture , here, in my humble opinion, everything was fine: splitting into favorite layers, Dependency Injection, implementation The presentation layer is like MVP, a familiar and frequently used Repository template for the data layer.
')
But besides the long-time favorite and familiar techniques in the design was a place and discoveries. It was this project that introduced me to the concept of Interactor (an object containing business logic for working with one or several entities), and it was here that the power of reactive programming opened up to me.

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

Familiarity with Android Clean Architecture led to the fact that any new project, as well as refactoring of existing ones, was reduced to three-layer, rxJava and MVP , and Interactors were used as the domain layer. There was an open question about the correct implementation of the transitions between the screens and here the notion of Router began to sound more and more often. At first, Router was single 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 seriously about navigation. All the main logic, including switching between the screens, is contained in the Presenter, respectively, 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 for each Activity and be transmitted to Presenter at creation.

And somehow, once again looking at the project, it was understood that we had VIPER - View, Interactor, Presenter, Entity and Router.

I think you noticed on the Observable scheme - this is where all the power of jet thrust is hidden. The data layer does not simply retrieve data from the remote or local storage in the necessary representation for us; it sends the entire sequence of actions to the Interactor wrapped in Observable, which in turn can continue this sequence at its discretion based on the task being implemented.

And now let's analyze a small example of the VIPER implementation for Android ( source code ):
Suppose that we are faced with the task of developing an application that once every three seconds requests a list of messages from a “not very flexible” server and displays the latest for each sender, and also notifies the user about new ones. By tapu on the last message, a list of all messages for the selected sender appears, but the messages still continue to synchronize with the server once every 3 seconds. Also from the main screen we can get to the contact list, and view all the messages for one of them.

And so, let's start, we have three screens: chats (latest messages from each contact), a list of messages from a particular contact and a list of contacts. Throw a class diagram:



The screens are fragments, the transitions between which are regulated by the Activity, which 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 a fragment, the base 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, which repeat the fragment life cycle.

public abstract class BasePresenter<View, Router> { 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 transfers 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 the fragments are switched.



As mentioned above, Router lives in an Activity, in a particular example, the MainActivity simply implements its interface, respectively, for each Activity its own Router, which controls the navigation between the fragments inside it, therefore each fragment in such an Activity should have a Presenter using the same Router : And so appeared BaseMainPresenter, which must be inherited by every Presenter working in MainActivity.

 public abstract class BaseMainPresenter<View extends BaseMainView> extends BasePresenter<View, MainRouter> { } 


When changing fragments in the MainActivity, the Toolbar and FloatingActionButton's state changes, so each fragment should be able to communicate the necessary state parameters to the Activity. To implement this 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 an 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 blanks in the form of such base classes significantly accelerates the process of creating new fragments for the MainActivity.

Router
But what turned out 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 launched for execution and unsubscribe from the running process:

 public abstract class Interactor<ResultType, ParameterType> { 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<ResultType> buildObservable(ParameterType parameter); public void execute(ParameterType parameter, Subscriber<ResultType> subscriber) { subscription.add(buildObservable(parameter) .subscribeOn(jobScheduler) .observeOn(uiScheduler) .subscribe(subscriber)); } public void unsubscribe() { subscription.clear(); } } 


Entity
For access to data, Interactor uses one or several DataProvider and forms rx.Observable for subsequent execution.

Task setting for the considered example included the need to make a periodic request to the server, which was easily implemented with the help of RX:

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


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

Conclusion
Architecture is the skeleton of the application, and if you forget about it, you can end up with a freak. A clear division of responsibility between layers and class types makes it easier to support, test, introduce a new person to a project, takes less time and adjusts to a uniform programming style. Base classes help avoid code duplication, and rx does not think about asynchrony. An ideal architecture, like an ideal code, is practically unattainable, but to strive for it means to grow professionally.

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

Source: https://habr.com/ru/post/277003/


All Articles