Android A few words about MVP + rxJava



When working with Android, you can often see how all the functional code fits into the activity / fragment lifecycle methods . In general, this approach has some justification - “life cycle methods” are just handlers that process the stages of creating a component by 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-so-"graceful" structure of the life cycle, its dependence on many launch flags, and the different (albeit similar) structure for different components, it is not always possible to effectively use such a separation, which ultimately results in writing all the code in onCreate () .

Model-View-Presenter + rxJava


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

  1. Model - is an entry point to the application data (often each screen has its own model). In this case, there should not be much difference where the data should come from - data from network requests or data from user interaction with the UI (clicks, swipes, etc.). A good place to implement handwritten caches. In conjunction with rxJava will be a set of methods that give 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 the connection between the processing of data received from the Model and the invocation of methods from the View, thereby realizing the response of the UI components to the data. Presenter methods are called from activity / fragment lifecycle methods and are often “symmetric” to them.

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

Example


Consider an example of a single-screen application that has EditText and TextView on it . At the same time, as text is being edited, EditText sends network requests, the result of which should be displayed in the TextView (the specifics of the request should not worry us, it can be a translation, a brief help on the term, or something similar).

ExampleModel.java :

public interface ExampleModel {  
    Observable changeText();  
    Observable 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:
  • As for the terminology: the article uses the same terminology as in the quite popular article about mvp for android from Antonio Leiva, here you can familiarize yourself with my very amateur translation.
  • Some were embarrassed by putting at the Model level a method giving Observable, which is responsible for events related to user actions (which leads to the need to implement a Model and contain an object connected by android view-shkami), but I still think it's correct and even convenient, because it allows you to talk about events editing the text for which processing is required, namely, as a stream of data that is associated with a UI only in a specific implementation. If this doesn’t suit you at all, then you can consider this very ureal example, that is, just part of the methods will go from Model to View. You can also consider an example from the article by Antonio Leiva, the connection of the 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 would be wise 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 that rxAndroid is used to “wrap” EditTetx , and retrofit is used to implement network requests.

ExampleModelImpl.java :

public class ExampleModelImpl implements ExampleModel {  
    private final ExampleViewHolder viewHolder;  
     public ExampleModelImpl(final ExampleViewHolder viewHolder) {  
        this.viewHolder = viewHolder;  
    }
    @Override  
    public Observable changeText() {  
        return WidgetObservable  
            .text(viewHolder.editText)  
            .map(new Func1() {  
                @Override  
                public String call(OnTextChangeEvent event) {  
                    return event.toString().trim();  
                }  
            });  
    }  
    @Override  
    public Observable 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 speed of typing (and it can be quite high), there is a natural desire to limit the frequency of events when editing text 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>() {  
                @Override  
                 public Observable call(String query) {  
                     return model.request(query);  
                 }  
            })
           .observeOn(AndroidSchedulers.mainThread())
           .subscribe(new Action1() {  
               @Override  
               public void call(String result) {  
                   view.showResponse(result);  
               }     
           });  
    }
    @Override  
    public void onDestroy() {  
        if (subscription != null) {  
            subscription.unsubscribe();  
         }  
    }  
}  


Implementation of activity that conveys the entire essential part of Presenter :

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 non-trivial points related to controlling the frequency of events. To imagine the evolution of our application in the context of mvp is quite easy:

  • Processing the lack of a network - is solved at the level of Model and View.
    Caching of query results is solved at the Model level (it is possible at the retrofit level, by configuring okhttp.Cache or HttpResponsecache, depending on what is used).
  • General error handling is solved at the Presenter level by adding an error handler for subscribe.
  • Logging decided depending on what must log in.
  • Creating a more complex UI, possibly animation - you need to modify ViewHolder, View.

Epilogue


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

Also popular now: