Android
Architecture MVP
Recherche…
Introduction
Cette rubrique fournira une architecture MVP (Model ‑ View ‑ Presenter) avec différents exemples.
Remarques
Il existe de nombreuses façons de concevoir une application Android. Mais tous ne sont pas testables et nous permettent de structurer notre code pour que l’application soit facile à tester. L'idée clé d'une architecture à tester est de séparer des parties de l'application, ce qui les rend plus faciles à entretenir, à étendre et à tester séparément les unes des autres.
Définition MVP
Modèle
Dans une application dotée d'une bonne architecture en couches, ce modèle ne serait que la passerelle vers la couche de domaine ou la logique métier. Voir comme le fournisseur des données que nous voulons afficher dans la vue.
Vue
La vue, généralement implémentée par une Activity
ou un Fragment
, contiendra une référence au présentateur . La seule chose à faire est d'appeler une méthode du présentateur chaque fois qu'il y a une action d'interface.
Présentateur
Le présentateur est responsable d'agir en tant qu'intermédiaire entre View et Model. Il récupère les données du modèle et les retourne au format de la vue. Mais contrairement au MVC typique, il décide également de ce qui se passe lorsque vous interagissez avec la vue.
* Définitions tirées de l'article d' Antonio Leiva.
Structure de l'application recommandée (non requise)
L'application doit être structurée par package par fonctionnalité . Cela améliore la lisibilité et modularise l'application de telle sorte que certaines parties peuvent être modifiées indépendamment les unes des autres. Chaque fonctionnalité clé de l'application se trouve dans son propre package Java.
Exemple de connexion dans le modèle MVP (Model View Presenter)
Voyons MVP en action en utilisant un simple écran de connexion. Il existe deux Button
: un pour l'action de connexion et un autre pour un écran d'enregistrement; deux EditText
: un pour le courrier électronique et l'autre pour le mot de passe.
LoginFragment (The View)
public class LoginFragment extends Fragment implements LoginContract.PresenterToView, View.OnClickListener {
private View view;
private EditText email, password;
private Button login, register;
private LoginContract.ToPresenter presenter;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_login, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
email = (EditText) view.findViewById(R.id.email_et);
password = (EditText) view.findViewById(R.id.password_et);
login = (Button) view.findViewById(R.id.login_btn);
login.setOnClickListener(this);
register = (Button) view.findViewById(R.id.register_btn);
register.setOnClickListener(this);
presenter = new LoginPresenter(this);
presenter.isLoggedIn();
}
@Override
public void onLoginResponse(boolean isLoginSuccess) {
if (isLoginSuccess) {
startActivity(new Intent(getActivity(), MapActivity.class));
getActivity().finish();
}
}
@Override
public void onError(String message) {
Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
}
@Override
public void isLoggedIn(boolean isLoggedIn) {
if (isLoggedIn) {
startActivity(new Intent(getActivity(), MapActivity.class));
getActivity().finish();
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.login_btn:
LoginItem loginItem = new LoginItem();
loginItem.setPassword(password.getText().toString().trim());
loginItem.setEmail(email.getText().toString().trim());
presenter.login(loginItem);
break;
case R.id.register_btn:
startActivity(new Intent(getActivity(), RegisterActivity.class));
getActivity().finish();
break;
}
}
}
LoginPresenter (Le présentateur)
public class LoginPresenter implements LoginContract.ToPresenter {
private LoginContract.PresenterToModel model;
private LoginContract.PresenterToView view;
public LoginPresenter(LoginContract.PresenterToView view) {
this.view = view;
model = new LoginModel(this);
}
@Override
public void login(LoginItem userCredentials) {
model.login(userCredentials);
}
@Override
public void isLoggedIn() {
model.isLoggedIn();
}
@Override
public void onLoginResponse(boolean isLoginSuccess) {
view.onLoginResponse(isLoginSuccess);
}
@Override
public void onError(String message) {
view.onError(message);
}
@Override
public void isloggedIn(boolean isLoggedin) {
view.isLoggedIn(isLoggedin);
}
}
LoginModel (Le modèle)
public class LoginModel implements LoginContract.PresenterToModel, ResponseErrorListener.ErrorListener {
private static final String TAG = LoginModel.class.getSimpleName();
private LoginContract.ToPresenter presenter;
public LoginModel(LoginContract.ToPresenter presenter) {
this.presenter = presenter;
}
@Override
public void login(LoginItem userCredentials) {
if (validateData(userCredentials)) {
try {
performLoginOperation(userCredentials);
} catch (JSONException e) {
e.printStackTrace();
}
} else {
presenter.onError(BaseContext.getContext().getString(R.string.error_login_field_validation));
}
}
@Override
public void isLoggedIn() {
DatabaseHelper database = new DatabaseHelper(BaseContext.getContext());
presenter.isloggedIn(database.isLoggedIn());
}
private boolean validateData(LoginItem userCredentials) {
return Patterns.EMAIL_ADDRESS.matcher(userCredentials.getEmail()).matches()
&& !userCredentials.getPassword().trim().equals("");
}
private void performLoginOperation(final LoginItem userCredentials) throws JSONException {
JSONObject postData = new JSONObject();
postData.put(Constants.EMAIL, userCredentials.getEmail());
postData.put(Constants.PASSWORD, userCredentials.getPassword());
JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, Url.AUTH, postData,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
try {
String token = response.getString(Constants.ACCESS_TOKEN);
DatabaseHelper databaseHelper = new DatabaseHelper(BaseContext.getContext());
databaseHelper.login(token);
Log.d(TAG, "onResponse: " + token);
} catch (JSONException e) {
e.printStackTrace();
}
presenter.onLoginResponse(true);
}
}, new ErrorResponse(this));
RequestQueue queue = Volley.newRequestQueue(BaseContext.getContext());
queue.add(request);
}
@Override
public void onError(String message) {
presenter.onError(message);
}
}
Diagramme de classe
Voyons l'action sous la forme d'un diagramme de classes.
Remarques:
- Cet exemple utilise Volley pour la communication réseau, mais cette bibliothèque n'est pas requise pour MVP
-
UrlUtils
est une classe qui contient tous les liens pour mes points de terminaison API -
ResponseErrorListener.ErrorListener
est uneinterface
quiErrorResponse
erreurs dansErrorResponse
quiimplements
Volley'sResponse.ErrorListener
; ces classes ne sont pas incluses ici car elles ne font pas directement partie de cet exemple
Exemple de connexion simple dans MVP
Structure de colis requise
XML activity_login
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<EditText
android:id="@+id/et_login_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="USERNAME" />
<EditText
android:id="@+id/et_login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="PASSWORD" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_login_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="4dp"
android:layout_weight="1"
android:text="Login" />
<Button
android:id="@+id/btn_login_clear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_weight="1"
android:text="Clear" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:text="correct user: mvp, mvp" />
<ProgressBar
android:id="@+id/progress_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp" />
</LinearLayout>
Classe d'activité LoginActivity.class
public class LoginActivity extends AppCompatActivity implements ILoginView, View.OnClickListener {
private EditText editUser;
private EditText editPass;
private Button btnLogin;
private Button btnClear;
private ILoginPresenter loginPresenter;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//find view
editUser = (EditText) this.findViewById(R.id.et_login_username);
editPass = (EditText) this.findViewById(R.id.et_login_password);
btnLogin = (Button) this.findViewById(R.id.btn_login_login);
btnClear = (Button) this.findViewById(R.id.btn_login_clear);
progressBar = (ProgressBar) this.findViewById(R.id.progress_login);
//set listener
btnLogin.setOnClickListener(this);
btnClear.setOnClickListener(this);
//init
loginPresenter = new LoginPresenterCompl(this);
loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_login_clear:
loginPresenter.clear();
break;
case R.id.btn_login_login:
loginPresenter.setProgressBarVisiblity(View.VISIBLE);
btnLogin.setEnabled(false);
btnClear.setEnabled(false);
loginPresenter.doLogin(editUser.getText().toString(), editPass.getText().toString());
break;
}
}
@Override
public void onClearText() {
editUser.setText("");
editPass.setText("");
}
@Override
public void onLoginResult(Boolean result, int code) {
loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
btnLogin.setEnabled(true);
btnClear.setEnabled(true);
if (result){
Toast.makeText(this,"Login Success",Toast.LENGTH_SHORT).show();
}
else
Toast.makeText(this,"Login Fail, code = " + code,Toast.LENGTH_SHORT).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
public void onSetProgressBarVisibility(int visibility) {
progressBar.setVisibility(visibility);
}
}
Créer une interface ILoginView
Créez une interface ILoginView
pour les informations de mise à jour de Presenter sous le dossier d'affichage, comme suit:
public interface ILoginView {
public void onClearText();
public void onLoginResult(Boolean result, int code);
public void onSetProgressBarVisibility(int visibility);
}
Création d'une interface ILoginPresenter
Créez une interface ILoginPresenter
afin de communiquer avec LoginActivity
(Views) et créez la classe LoginPresenterCompl
pour gérer la fonctionnalité de connexion et rendre compte de l'activité. La classe LoginPresenterCompl
implémente l'interface ILoginPresenter
:
ILoginPresenter.class
public interface ILoginPresenter {
void clear();
void doLogin(String name, String passwd);
void setProgressBarVisiblity(int visiblity);
}
LoginPresenterCompl.class
public class LoginPresenterCompl implements ILoginPresenter {
ILoginView iLoginView;
IUser user;
Handler handler;
public LoginPresenterCompl(ILoginView iLoginView) {
this.iLoginView = iLoginView;
initUser();
handler = new Handler(Looper.getMainLooper());
}
@Override
public void clear() {
iLoginView.onClearText();
}
@Override
public void doLogin(String name, String passwd) {
Boolean isLoginSuccess = true;
final int code = user.checkUserValidity(name,passwd);
if (code!=0) isLoginSuccess = false;
final Boolean result = isLoginSuccess;
handler.postDelayed(new Runnable() {
@Override
public void run() {
iLoginView.onLoginResult(result, code);
}
}, 5000);
}
@Override
public void setProgressBarVisiblity(int visiblity){
iLoginView.onSetProgressBarVisibility(visiblity);
}
private void initUser(){
user = new UserModel("mvp","mvp");
}
}
Créer un UserModel
Créez un UserModel
qui ressemble à une classe Pojo pour LoginActivity
. Créez une interface IUser
pour les validations Pojo:
UserModel.class
public class UserModel implements IUser {
String name;
String passwd;
public UserModel(String name, String passwd) {
this.name = name;
this.passwd = passwd;
}
@Override
public String getName() {
return name;
}
@Override
public String getPasswd() {
return passwd;
}
@Override
public int checkUserValidity(String name, String passwd){
if (name==null||passwd==null||!name.equals(getName())||!passwd.equals(getPasswd())){
return -1;
}
return 0;
}
IUser.class
public interface IUser {
String getName();
String getPasswd();
int checkUserValidity(String name, String passwd);
}
MVP
Un modèle-vue-présentateur (MVP) est une dérivation du modèle architectural modèle – vue-contrôleur (MVC). Il est principalement utilisé pour créer des interfaces utilisateur et offre les avantages suivants:
- Les vues sont plus séparées des modèles. Le présentateur est le médiateur entre le modèle et la vue.
- Il est plus facile de créer des tests unitaires.
- En général, il existe une correspondance univoque entre View et Presenter, avec la possibilité d'utiliser plusieurs présentateurs pour des vues complexes.