Dans le didacticiel précédent, nous avons parlé du modèle Model View Presenter, de son application sur Android et de ses avantages les plus importants. Dans ce tutoriel, nous explorons plus en détail le modèle Model View Presenter en l'implémentant dans une application Android..
Dans ce tutoriel:
Le modèle Model View Presenter est un modèle architectural basé sur le modèle MVC (Model View Controller) qui augmente la séparation des problèmes et facilite les tests unitaires. Il crée trois couches, Modèle, Vue, et Présentateur, chacun avec une responsabilité bien définie.
Le modèle contient la logique métier de l'application. Il contrôle la manière dont les données sont créées, stockées et modifiées. La vue est une interface passive qui affiche des données et achemine les actions des utilisateurs vers le présentateur. Le présentateur joue le rôle d'intermédiaire. Il extrait les données du modèle et les affiche dans la vue. Il traite également les actions de l'utilisateur transmises par la vue..
Nous allons construire une application de notes simple pour illustrer MVP. L'application permet à l'utilisateur de prendre des notes, de les enregistrer dans une base de données locale et de supprimer des notes. Pour faire simple, l'application aura une seule activité.
Dans ce tutoriel, nous nous concentrons principalement sur la mise en œuvre du modèle MVP. D'autres fonctions, telles que la configuration d'une base de données SQLite, la création d'un DAO ou la gestion de l'interaction utilisateur, sont ignorées. Si vous avez besoin d’aide sur l’un de ces sujets, Envato Tuts + propose d’excellents tutoriels sur ces sujets..
Commençons par la création d'une nouvelle note. Si nous scindons cette action en opérations plus petites, voici à quoi ressemblerait le flux utilisant le modèle architectural MVP:
Remarque
object avec le texte saisi par l'utilisateur et demande au modèle de l'insérer dans la base de données.Examinons maintenant les opérations nécessaires pour réaliser cette action et séparons-les à l'aide de MVP. Pour que les différents objets soient faiblement couplés, la communication entre les couches s'effectue à l'aide d'interfaces. Nous avons besoin de quatre interfaces:
RequiredViewOps
: requis Voir les opérations disponibles pour le présentateurFourniPresenterOps
: opérations offertes à View pour la communication avec le présentateurRequiredPresenterOps
: opérations de présentateur requises disponibles pour le modèleFourniModèleOps
: opérations offertes à Model pour communiquer avec le présentateurMaintenant que nous avons une idée de la manière dont les différentes méthodes doivent être organisées, nous pouvons commencer à créer notre application. Nous simplifions la mise en œuvre en nous concentrant uniquement sur l'action permettant d'ajouter une nouvelle note. Les fichiers sources de ce tutoriel sont disponibles sur GitHub.
Nous utilisons un seul Activité
avec une mise en page qui comprend:
Éditer le texte
pour les nouvelles notesBouton
ajouter une noteRecyclerVoir
pour lister toutes les notesAffichage
éléments et un Bouton
à l'intérieur d'un RecyclerVoir
titulaireCommençons par créer les interfaces. Pour que tout reste organisé, nous plaçons les interfaces dans un support. Encore une fois, dans cet exemple, nous nous concentrons sur l'action pour ajouter une nouvelle note.
interface publique MVP_Main / ** * Méthodes d'affichage requises disponibles pour le présentateur. * Une couche passive, chargée d'afficher les données * et de recevoir les interactions utilisateur * / interface RequiredViewOps // Afficher les opérations autorisées dans le contexte du présentateur getAppContext (); Contexte getActivityContext (); void notifyItemInserted (int layoutPosition); void notifyItemRangeChanged (int positionStart, int itemCount); / ** * Opérations offertes à View pour communiquer avec Presenter. * Traite les interactions des utilisateurs, envoie des requêtes de données à Model, etc. * / interface ProvidedPresenterOps // Opérations du présentateur autorisées à Afficher void clickNewNote (EditText editText); // configuration de recycler adapter int getNotesCount (); NotesViewHolder createViewHolder (parent de ViewGroup, int viewType); annuler bindViewHolder (titulaire de NotesViewHolder, position int); / ** * Méthodes de présentateur requises disponibles pour Model. * / interface RequiredPresenterOps // Opérations du présentateur autorisées dans le contexte de modèle getAppContext (); Contexte getActivityContext (); / ** * Opérations offertes à Model pour communiquer avec Presenter * Gère toute la logique métier des données. * / interface ProvidedModelOps // Opérations de modèle autorisées par le présentateur int getNotesCount (); Notez getNote (int position); int insertNote (note note); boolean loadData ();
Il est maintenant temps de créer les couches Modèle, Vue et Présentateur. Puisque Activité principale
agira comme la vue, il devrait mettre en œuvre la RequiredViewOps
interface.
Classe publique MainActivity extend AppCompatActivity implémente View.OnClickListener, MVP_Main.RequiredViewOps privé MVP_Main.ProvidedPresenterOps mPresenter; private EditText mTextNewNote; private ListNotes mListAdapter; @Override public void onClick (Affichage v) switch (v.getId ()) case R.id.fab: // Ajoute une nouvelle note mPresenter.clickNewNote (mTextNewNote); @Override public Context getActivityContext () return this; Contexte public @Override getAppContext () return getApplicationContext (); // Informez RecyclerAdapter qu'un nouvel élément a été inséré @Override public void notifyItemInserted (int adapterPos) mListAdapter.notifyItemInserted (adapterPos); // notifie à RecyclerAdapter que des éléments ont changé @Override public void notifyItemRangeChanged (int positionStart, int itemCount) mListAdapter.notifyItRemChanged (positionStart, itemCount); // notifiez RecyclerAdapter que l'ensemble de données a changé @Override public void notifyDataSetChanged () mListAdapter.notifyDataSetChanged (); // Adaptateur Recycler // Cette classe peut avoir son propre présentateur, mais par souci de simplicité, utilisera un seul présentateur. // L'adaptateur est passif et tout le traitement a lieu // dans la couche Presenter. la classe privée ListNotes étend RecyclerView.Adapter@Override public int getItemCount () return mPresenter.getNotesCount (); @Override public NotesViewHolder onCreateViewHolder (ViewGroup parent, int viewType) return mPresenter.createViewHolder (parent, viewType); @Override public void onBindViewHolder (détenteur de NotesViewHolder, int position) mPresenter.bindViewHolder (détenteur, position);
Le présentateur est l'intermédiaire et doit implémenter deux interfaces:
FourniPresenterOps
autoriser les appels depuis la vueRequiredPresenterOps
recevoir les résultats du modèlePortez une attention particulière à la référence de la couche Vue. Nous devons utiliser un Faible référence
puisque Activité principale
pourrait être détruit à tout moment et nous voulons éviter les fuites de mémoire. En outre, la couche modèle n'a pas encore été configurée. Nous le faisons plus tard lorsque nous connectons les couches MVP ensemble.
La classe publique MainPresenter implémente MVP_Main.ProvidedPresenterOps, MVP_Main.RequiredPresenterOps // Voir la référence. Nous utilisons comme référence faible // parce que l'activité peut être détruite à tout moment // et nous ne voulons pas créer de fuite de mémoire privée WeakReferencemView; // Référence de modèle private MVP_Main.ProvidedModelOps mModel; / ** * Constructeur du présentateur * @param view MainActivity * / public MainPresenter (vue MVP_Main.RequiredViewOps) mView = new WeakReference <> (view); / ** * Retourne la référence de la vue. * Lancer une exception si la vue n'est pas disponible. * / private MVP_Main.RequiredViewOps getView () lève NullPointerException if (mView! = null) return mView.get (); sinon, lance la nouvelle NullPointerException ("La vue n'est pas disponible"); / ** * Récupère le nombre total de notes dans le modèle * @return Taille de la liste de notes * / @Override public int getNotesCount () return mModel.getNotesCount (); / ** * Crée le détenteur RecyclerView et configure sa vue * @param parent Recycler viewGroup * @param viewType Type de détenteur * @return Recycler ViewHolder * / @Override public NotesViewHolder createViewHolder (ViewGroup parent, int viewType) NotesViewHolder viewHolder; LayoutInflater inflater = LayoutInflater.from (parent.getContext ()); View viewTaskRow = inflater.inflate (R.layout.holder_notes, parent, false); viewHolder = new NotesViewHolder (viewTaskRow); retourne viewHolder; / ** * Lie ViewHolder avec RecyclerView * Titulaire @param Titulaire à lier * Position @param Position sur l'adaptateur Recycler * / @Override public null bindViewHolder (détenteur final de NotesViewHolder, int position) note finale Remarque = mModel.getNote (position) ; titulaire.text.setText (note.getText ()); holder.date.setText (note.getDate ()); holder.btnDelete.setOnClickListener (nouvelle View.OnClickListener () @Override public void onClick (Voir v) clickDeleteNote (note, holder.getAdapterPosition (), holder.getLayoutPosition ());; / ** * @return Contexte d'application * / @Override contexte public getAppContext () try return getView (). getAppContext (); catch (NullPointerException e) return null; / ** * @return Contexte d'activité * / @Override contexte public getActivityContext () try return getView (). getActivityContext (); catch (NullPointerException e) return null; / ** * Appelé par vue lorsque l'utilisateur clique sur le nouveau bouton Note. * Crée une note avec du texte saisi par l'utilisateur et demande à * Model de l'insérer dans la base de données. * @param editText EditText avec le texte saisi par l'utilisateur * / @Override public void clickNewNote (final EditText editText) getView (). showProgress (); final String noteText = editText.getText (). toString (); if (! noteText.isEmpty ()) new AsyncTask () @Override protected Integer doInBackground (Void… params) // Insère une note dans Model, renvoyant le retour de la position de l'adaptateur mModel.insertNote (makeNote (noteText)); @Override protected void onPostExecute (Integer adapterPosition) try if (adaptateurPosition> -1) // Note insérée getView (). ClearEditText (); getView (). notifyItemInserted (adapterPosition + 1); getView (). notifyItemRangeChanged (adapterPosition, mModel.getNotesCount ()); else // Informe sur l'erreur getView (). hideProgress (); getView (). showToast (makeToast ("Erreur lors de la création de la note [" + noteText + "]")); catch (NullPointerException e) e.printStackTrace (); .execute (); else try getView (). showToast (makeToast ("Impossible d'ajouter une note vide!")); catch (NullPointerException e) e.printStackTrace (); / ** * Crée un objet Note avec le texte donné * @param noteText Chaîne avec le texte Note * @return Un objet Note * / public Note makeNote (String noteText) Note note = new Note (); note.setText (noteText); note.setDate (getDate ()); note de retour;
La couche modèle est responsable de la gestion de la logique applicative. Il détient un ArrayList
avec les notes ajoutées à la base de données, une référence DAO pour effectuer des opérations sur la base de données et une référence à l'animateur.
Classe publique MainModel implémente MVP_Main.ProvidedModelOps // Référence du présentateur private MVP_Main.RequiredPresenterOps mPresenter; DAO mDAO privé; // Recycler data public ArrayListmNotes; / ** * Constructeur principal, appelé par Activity lors de l'installation de MVP * @param presenter Instance de présentateur * / public MainModel (présentateur MVP_Main.RequiredPresenterOps) this.mPresenter = presenter; mDAO = new DAO (mPresenter.getAppContext ()); / ** * Insère une note sur le DB * @param note pour insérer la note de * @return sur ArrayList * / @Override public int insertNote (Note note) Note inséréeNote = mDAO.insertNote (note); if (insertNote! = null) loadData (); retourne getNotePosition (insertNote); return -1; / ** * Charge toutes les données et obtient les notes de la base de données * @return true avec succès * / @Override public boolean loadData () mNotes = mDAO.getAllNotes (); return mNotes! = null; / ** * Obtient une note spécifique de la liste de notes en utilisant sa position dans le tableau * @param position Position du tableau * @return Note de la liste * / @Override public Note getNote (position int) return mNotes.get (position); / ** * Obtenir la taille de ArrayList * @return taille de ArrayList * / @Override public int getNotesCount () if (mNotes! = Null) return mNotes.size (); retourne 0;
Avec les couches MVP en place, nous devons les instancier et insérer les références nécessaires. Avant cela, nous devons traiter quelques problèmes directement liés à Android..
Parce qu'Android ne permet pas l'instanciation d'un Activité
, la couche View sera instanciée pour nous. Nous sommes responsables de l'instanciation des couches Presenter et Model. Malheureusement, instancier ces couches en dehors de la Activité
peut être problématique.
Pour ce faire, il est recommandé d’utiliser une forme d’injection de dépendance. Puisque notre objectif est de nous concentrer sur la mise en œuvre de MVP, nous adopterons une approche plus simple. Ce n’est pas la meilleure approche disponible, mais c’est la plus facile à comprendre. Nous discuterons de l'injection de MVP et de dépendance plus tard dans cette série.
RequiredViewOps
et FourniModèleOps
dans le présentateurRequiredPresenterOps
dans le modèleFourniPresenterOps
comme référence à utiliser dans la vue/ ** * Configuration du modèle Voir le modèle du présentateur * / private void setupMVP () // Créer le présentateur MainPresenter presenter = new MainPresenter (this); // Créer le modèle MainModel model = new MainModel (présentateur); // Définir le modèle Presenter presenter.setModel (model); // Définit le présentateur comme interface mPresenter = presenter;
Une autre chose à considérer est le cycle de vie de l'activité. Android Activité
peut être détruit à tout moment et les calques Presenter et Model peuvent également être détruits. Nous devons résoudre ce problème en utilisant une sorte de machine à états pour sauvegarder l'état lors des changements de configuration. Nous devrions également informer les autres couches de l'état de l'activité.
Pour ce faire, nous allons utiliser une classe séparée, StateMaintainer
, qui contient un fragment qui conserve son état et utilise ce fragment pour sauvegarder et récupérer nos objets. Vous pouvez jeter un coup d'œil à l'implémentation de cette classe dans les fichiers source de ce tutoriel..
Nous devons ajouter un onDestroy
présentateur et le modèle pour les informer de l’état actuel de l’activité. Nous devons également ajouter un setView
méthode au présentateur, qui sera chargé de recevoir une nouvelle référence de vue de l'activité recréée.
La classe publique MainActivity étend AppCompatActivity implémente View.OnClickListener, MVP_Main.RequiredViewOps //… private void setupMVP () // Vérifie si StateMaintainer a été créé si (mStateMaintainer.firstTimeIn ()) // Créer le présentateur MainPresenter présentateur, nouveau présent. (ce); // Créer le modèle MainModel model = new MainModel (présentateur); // Définir le modèle Presenter presenter.setModel (model); // Ajouter le présentateur et le modèle à StateMaintainer mStateMaintainer.put (présentateur); mStateMaintainer.put (modèle); // Définit le présentateur comme interface // Pour limiter la communication avec lui mPresenter = presenter; // récupère le présentateur auprès de StateMaintainer else // récupère le présentateur mPresenter = mStateMaintainer.get (MainPresenter.class.getName ()); // Mise à jour de la vue dans Presenter mPresenter.setView (this); //…
Le modèle MVP est capable de résoudre certains problèmes causés par l'architecture par défaut d'Android. Cela rend votre code facile à maintenir et à tester. L'adoption de MVP peut sembler difficile au début, mais une fois que vous avez compris la logique derrière tout cela, le processus est simple..
Vous pouvez maintenant créer votre propre bibliothèque MVP ou utiliser une solution déjà disponible, telle que Mosby ou simple-mvp. Vous devriez maintenant mieux comprendre ce que ces bibliothèques font en coulisses.
Nous sommes presque à la fin de notre parcours MVP. Dans la troisième et dernière partie de cette série, nous ajouterons les tests unitaires au mélange et adapterons notre code pour utiliser l'injection de dépendances avec Dagger. J'espère te voir là-bas.