📜 ⬆️ ⬇️

Android A few words about MVP + rxJava



Working with Android, it is often possible to see how the entire functional code fits into the activity / fragment lifecycle methods. In general, such an approach has some justification - “life-cycle methods” are just handlers that process the steps of creating a component with a system and are specifically designed to fill them with code. Adding here that the UI framework is described through xml files, we already get the basic separation of logic and interface. However, due to the not quite “elegant” structure of the life cycle, its dependence on a variety of launch flags, and a different (albeit similar) structure for different components, it’s not always possible to effectively use this separation, which ultimately results in writing all the code onCreate () .

Model-View-Presenter + rxJava


MVP development pattern for android, offering to split the application into the following parts:

  1. Model - represents the entry point to the application data (often on each screen its own model). In this case, much of a difference from where the data should not be - the data of network requests or user interaction data with UI (clicks, swipe, etc.). A good place to implement handwritten caches. In conjunction with rxJava, it will be a set of methods that render Observable.
  2. View - is a class that sets the state of UI elements. Do not confuse the term with android.view.View
  3. Presenter - establishes a link between processing the data received from the Model and calling methods on the View, thus realizing the reaction of the UI components to the data. Presenter methods are called from the life / fragment lifecycle methods and are often "symmetrical" to them.

Model / View / Presenter should be interfaces for more flexibility in modifying code.
')

Example


Consider an example of an application consisting of a single screen, which contains EditText and TextView . At the same time, as you edit the text, network requests are sent to the EditText , the result of which should be displayed in the TextView (the specifics of the request should not worry us, it may be a translation, a brief reference to the term or something similar).

ExampleModel.java :

public interface ExampleModel { Observable<String> changeText(); Observable<String> request(String query); } 

ExampleView.java :

 public interface ExampleView { void showResponse(String result); } 

ExamplePresenter.java :

 public interface ExamplePresenter { void onCreate(Activity activity, Bundle savedInstanceState); } 

Implementation


Explanations
Since there were some serious comments in the comments, I think it is reasonable to make some notes:
  • Regarding the terminology: the article uses the same terminology as in quite a popular article about mvp for android from Antonio Leiva, here you can get acquainted with my, very much amateur translation.
  • Some were confused by the placement on the Model level and the method that gives Observable, which is responsible for events related to the user's actions (which leads to the need to implement the Model and contain an object associated with android view-shots), but I still think this is correct and even convenient, because it allows you to talk about events editing text that requires processing, namely, as the data flow, which are only in a specific implementation associated with the UI-it. If you are not fundamentally satisfied with this, then you can consider this very ureazny example; simply, some of the methods will go from Model to View. You can also consider an example from the article by Antonio Leiva, the connection of components there is such M <= P <=> V


Since Model and View use the same widgets for you (in our case, EditText and TextView ) for their work, it will be reasonable to implement the class containing them.

ExampleViewHolder.java :

 public class ExampleViewHolder { public final EditText editText; public final TextView textView; public ExampleViewHolder(EditText editText, TextView textView) { this.editText = editText; this.textView = textView; } } 

When implementing the Model, we assume the use of rxAndroid, for wrapping up EditTetx , and retrofit for implementing network requests.

ExampleModelImpl.java :

 public class ExampleModelImpl implements ExampleModel { private final ExampleViewHolder viewHolder; public ExampleModelImpl(final ExampleViewHolder viewHolder) { this.viewHolder = viewHolder; } @Override public Observable<String> changeText() { return WidgetObservable .text(viewHolder.editText) .map(new Func1<OnTextChangeEvent, String>() { @Override public String call(OnTextChangeEvent event) { return event.toString().trim(); } }); } @Override public Observable<String> request(String query) { //     retrofit return RestManager.newInstance().request(query); } } 


ExampleViewImpl.java :

 public class ExampleViewImpl implements ExampleView { private final ExampleViewHolder viewHolder; public ExampleViewImpl(final ExampleViewHolder viewHolder) { this.viewHolder = viewHolder; } @Override public void showResponse(final String result) { viewHolder.textView.setText(result); } } 


Since the number of network requests depends on the typing speed (and it can be quite high), there is a natural desire to limit the frequency of editing text editing events in EditText . In this case, this is implemented by the debounce operator (in this case, of course, text input is not blocked, but only part of the editing events that occurred in a time interval of 150 milliseconds is skipped).

ExamplePresenterImpl.java :

 public class ExamplePresenterImpl implements ExamplePresenter { private final ExampleModel model; private final ExampleView view; private Subscription subscription; public ExamplePresenterImpl(ExampleModel model, ExampleView view) { this.model = model; this.view = view; } @Override public void onCreate(Activity activity, Bundle savedInstanceState) { subscription = model .changeText() //   .debounce(150, TimeUnit.MILLISECONDS) .switchMap(new Func1<String, Observable<String>>() { @Override public Observable<String> call(String query) { return model.request(query); } }) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<String>() { @Override public void call(String result) { view.showResponse(result); } }); } @Override public void onDestroy() { if (subscription != null) { subscription.unsubscribe(); } } } 


An implementation of activity that conveys all the essential part of Presenter work:

ExampleActivity.java
 public class ExampleActivity extends Activity { private ExamplePresenter examplePresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.example_activity); final ExampleViewHolder exampleViewHolder = new ExampleViewHolder( (TextView) findViewById(R.id.text_view), (EditText) findViewById(R.id.edit_text) ); final ExampleModel exampleModel = new ExampleModelImpl(exampleViewHolder); final ExampleView exampleView = new ExampleViewImpl(exampleViewHolder); examplePresenter = new ExamplePresenterImpl(exampleModel, exampleView); examplePresenter.onCreate(this, savedInstanceState); } @Override protected void onDestroy() { super.onDestroy(); examplePresenter.onDestroy(); } } 

Conclusion


Although our example is incredibly simplified, it already has some non-trivial points related to controlling the frequency of events. It’s pretty easy to imagine the evolution of our application in terms of mvp:


Epilogue


MVP is not the only way to partition an Android application into components, and even more so it does not necessarily imply the use of rxJava with it. However, their simultaneous use gives acceptable results in simplifying the structure of the supported application.

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


All Articles