- Rx - for handling complex parallel/async tasks ( server calls and database operations).
- realm - as a database
model-layer
hosted locally on device as a set of tables in text files). - backendless - also
model-layer
hosted remotely on backendless server as set of tables. - retrolambda - for adding Java8 lambdas compatible with 1.6, 1.7.
MVP refers to an abstract project architecture narrowed down to the relations and communicating between Model
, View
,ViewState
and Presenter
We DO NOT communicate between fragments,activities,toolbars,views,services etc. Instead, we abstract to communicate between M, V and P only - that's the main point which simplifies the whole architecture a lot.
View1
->Presenter1
->model
(save to db, because db triggers all listeners)->Presenter2
->View2
Don't be scared of this squares. Imagine a Cafe, where you make your orders using iPad embedded in every table.
Then:
Models
include kitchen, fridge, coffee machine, billing system, vip members list, etc...ViewState
role goes to Waiter.View
is a Cafe's Vision of a Customer or in other words, a set of all possible actions that Cafe wants to perform on customers (0+ times).Presenter
is an iPad : showing menu, taking orders, processing bills; it handles all data i/o between:- customers (
View
== anyone who implementsCustomerView
's methods) - waiter (
ViewState
); - kitchen, billing system, etc... (
Model
)
- customers (
presenter
delivers results of cooking (cafe commands) to CustomerView with the help of Waiter .
Please think of
View
in terms of the system designer's vision_ of the process.That kind of meaning. Not the synonym of 'look' or 'widget'.
People are different. Cafe visitors are different. App users are different. But our aim is to find such typical actions that do not depend on the difference between customers/users. That's one of the most importance. Both visitor and organisation can use cafe : use menu, place an order, pay bill. And to succeed the process there is no difference for cafe how old is the organisation or what annual income the visitor has.
- Let's compare cafe visit to adding MVP functional to app component:
Property | Real Cafe | Android app using Moxy-MVP
------------ | ------------ | -------------
Entity we use with MVP | Customer | ? implements CustomerView
subject | A person, group, organisation | Fragment
, Activity
, CustomView+MvpDelegate
subject qualifier | Willing to use cafe's service | Implemented CustomerView
with all it's methods
MVP-important behaviour | Can wait, order,smile, eat, pay, leave| Various UI changes on screen - display text,image, widget
MVP-independent actions | go peeing, answer call, browse memes | Incoming calls, power, no network, system update, app switch
The main rule is to allow cafe see a Customer-like behaviour described in Cafe's vision (
View
) of a typical visitor.
Imagine yourself in cafe with a friend. Think of it as a good place to attach another customer to same iPad; It will easily handle same operations for second customer on the same table, independent of the fact, that your friends reaction will differ from yours; And properties (_age, gender, appettite) usually differs from yours. That's just doesn't play any role in MVP angle of this process.
-
You have an initial state : your eyes render hungry, your whole body is running
performLongWaitAnimation()
-
iPad (
presenter
) presents a menu:Customer places order on coffee. Customer places order on donut. Customer confirms the whole order.
-
iPad receives onOrderReceived callback with data.
iPad's perspective: -> onOrderReceived( List<MenuItem> orderedItems);
-
presenter
beginsnew Async( ()->launchCoffeeMachine("latte") )
,-> model.coffeemachine.makeEspresso() //prepare coffee
-
presenter
launches another syncronous operation -fridge.retrieveDonut()
,-> get donut from `model.openFridge().getDonut(4,20)` -> send Waiter with donut to Customer
-
presenter
listens to events:- from
model.getCoffeeMachine()
- when its ready- iPad issues a command (tells waiter coffee to move coffee frommodel:machine
to Customer; - from
CustomerView
callbacks:- Customer performs Eating.
- When finished - CustomerView calls back to presenter to ask a bill:
mPresenter.onEatFinished();
- from
- What part of your code to consider
model
s andview
s - Whether to fit code to MVP at all;
Mixing MVP and non-MVP is already good, while moving more code to MVP is still better.
Nevertheless there are some tactics which allow to drastically reduce the volume of code needed to create a system if and only if you define the roles of your classes according to some rules - let's call them Best practices
model
- is literally anything where we can read/write/get/set/download/upload/configure/change any digital data:
- online data input/output/storage:
- retrofit
- backendless
- rest api's
- offline storage create/read/update/delete (CRUD):
- realm
- SQLite
- SharedPreferences, etc...
- deivce services:
- intents
- services
- geo
- display
- audio
- camera, etc...
- device or 3rd-party sensors:
- gyroscope
- light sensor
- accelerometer
- microphone
- compass
- EMV sensor
- wifi, Bluetooth, connected gadgets etc.
if there is something we can get data from - we want to use it as a
model-layer
in this architecture). No specific MVP code is needed to consider any data source amodel
. Use the same code you would use if you write non-MVP. The only rule here is to interact withmodel
from presenter! This will make the magic of commands history in ViewState
view
- is an interface
defining what action some entity (device screen, RelativeLayout, widget, sound device) is meant to be able to perform.
Each and every Customer, including you, implements CustomerView
- defines how this entity reacts to events which might happen in cafe (defined above^).
- define interface (make it
implement MvpView
)
public interface CustomerView implements MvpView {
void welcome(String greetingsPhrase);
void assignSeat (int tableNumber);
void presentMenu (List<MenuItems> menu);
void orderReady(MenuItem cookedItem);
void showBill(Map<MenuItem,String> chequeBiu);
void auRevoir();
}
- Now you take
Activity
,Fragment
or anyCustomView+delegate
and addimplements CustomerView
to make them be a 100% View V of MVP.@InjectPresenter MvpPresenter myCustomerPresenter;
public class MyFragment extends MvpAppCompatFragment implements CustomerView {
@InjectPresenter
MyMoxyPresenter myPresenterObject;
@Override
public void onCreate{
...
}
@Override
void welcome( String greetingsPhrase ){
greet_tv.setText( greetingsPhrase )
}
...
}
The above @InjectPresenter annotation tells moxy to generate ViewState
class for this View
implementor.
Important :
android.View
is totally different fromView
V of MVP . Really. Not samesame kind of stuff.
First, it is generated automatically by Moxy and it works perfect.
@InjectsPresenter - generates ViewState.
ViewState is a class which :
- holds the current state of
view
and also history of commands frompresenter
toviews
. - manages the activity/fragments lifecycle mess for you - and makes it perfectly.
Presenter is a java class which implements MvpPresenter
- Methods defining how you retrieve/save data from
model
(model
== code talking to):
-
Methods to manipulate the data (so-called business logic of the app)
-
Callbacks - write methods you will call from View, when events( eg. clicks, touches, end of animation) happen there.
Presenter
methods examples:
-
Type 1 (CRUD)
-
local
private List<Map> getLocalMaps(){
Model model = getLocalRepository();
return model.where(Map.class).findAll();
}
- remote
private refreshMaps(){
Model model = Backendless.Persistence();
model.of(Map.class)
.where("objectId",map.id)
.find( results -> { getLocalModel().copyOrupdate(results); });
}
- Type 2 (logic):
private Map incrementLikes(Map map){
int oldLikes = map.getLikes(); // inner data manipulations
map.setLikes( oldLikes + 1);
return map;
}
onSettingsClickedFromActivity(){
UserConfig currentSettings = model.getConfigs(); //retrieve up-to-date configs from db
//View(State) We send the configs object to view;
// View in it's turn has a databinded layout which takes userConfig
// and displays it's variables we took from db - that's the job of presenter as designed;
getViewState().displaySettingsWindowUsingConfig(config);
}
-
type 3 (callbacks)
- simple:
onNewLevel(){ getViewState().showSuccessAnimation(); getViewState().displayAd(); }
- mixed:
onLikeButtonClicked(int mapId){ //type 3: this method is called from activity,(like btn ClickListener) getViewState().runLikeAnimation(); // ui command ( View's method) getViewState().showBackgroundProgress(); // ui command ( View's method) map = getLocalMaps().getById(mapId); // type 1 - retrieving incrementLikes(map); // type 2 - logic model.saveToDatabase(map); // type 1 - saving updated map to db getViewState().hideBackgroundProgress(); // ui command ( View's method) }
to be continued...