So übernehmen Sie Model View Presenter auf Android

In dem vorherigen Tutorial haben wir über das Model View Presenter-Muster, seine Anwendung auf Android und seine wichtigsten Vorteile gesprochen. In diesem Lernprogramm wird das Model View Presenter-Muster ausführlicher untersucht, indem es in einer Android-Anwendung implementiert wird.

In diesem Tutorial:

  • Wir erstellen eine einfache Anwendung mit dem MVP-Muster
  • Wir untersuchen, wie Sie das MVP-Muster auf Android implementieren können
  • und wir besprechen, wie einige Schwierigkeiten durch die Android-Architektur gelöst werden können

1. Model View Presenter

Das Model View Presenter-Muster ist ein Architekturmuster, das auf dem Model View Controller (MVC) -Muster basiert, das die Trennung von Anliegen erhöht und das Testen von Einheiten erleichtert. Es entstehen drei Schichten, Modell, Aussicht, und Moderator, jeder mit einer klar definierten Verantwortung.

Das Modell enthält die Geschäftslogik der Anwendung. Sie steuert, wie Daten erstellt, gespeichert und geändert werden. Die Ansicht ist eine passive Schnittstelle, die Daten anzeigt und Benutzeraktionen an den Presenter weiterleitet. Der Presenter fungiert als Mittelsmann. Es ruft Daten aus dem Modell ab und zeigt sie in der Ansicht. Es verarbeitet auch Benutzeraktionen, die von der View weitergeleitet werden.

2. Projektierung und Einrichtung

Wir werden eine einfache Notizanwendung erstellen, um MVP zu veranschaulichen. Mit der App können Benutzer Notizen machen, in einer lokalen Datenbank speichern und Notizen löschen. Um es einfach zu machen, wird die App nur eine Aktivität haben.

In diesem Tutorial konzentrieren wir uns hauptsächlich auf die Implementierung des MVP-Musters. Andere Funktionen wie das Einrichten einer SQLite-Datenbank, das Erstellen eines DAO oder die Handhabung der Benutzerinteraktion werden übersprungen. Wenn Sie bei einem dieser Themen Hilfe benötigen, bietet Envato Tuts + einige großartige Tutorials zu diesen Themen.

Aktionsdiagramm und MVP-Layer

Beginnen wir mit der Erstellung einer neuen Notiz. Wenn wir diese Aktion in kleinere Operationen unterteilen, würde der Fluss mit dem MVP-Architekturmuster so aussehen:

  • Der Benutzer gibt eine Notiz ein und klickt auf die Schaltfläche Notiz hinzufügen.
  • Der Presenter erstellt eine Hinweis Objekt mit dem vom Benutzer eingegebenen Text und fordert das Modell auf, ihn in die Datenbank einzufügen.
  • Das Modell fügt die Notiz in die Datenbank ein und informiert den Presenter, dass sich die Liste der Notizen geändert hat.
  • Der Presenter löscht das Textfeld und fordert die Ansicht auf, ihre Liste zu aktualisieren, um die neu erstellte Notiz anzuzeigen.

MVP-Schnittstellen

Betrachten wir nun die Operationen, die zum Erreichen dieser Aktion erforderlich sind, und trennen Sie sie mithilfe von MVP. Um die verschiedenen Objekte lose miteinander verbunden zu halten, erfolgt die Kommunikation zwischen den Schichten über Schnittstellen. Wir brauchen vier Schnittstellen:

  • RequiredViewOps: erforderlich Für Presenter verfügbare Vorgänge
  • BereitgestelltPresenterOps: Operationen, die View zur Kommunikation mit Presenter angeboten werden
  • RequiredPresenterOps: Erforderliche Presenter-Vorgänge, die Model zur Verfügung stehen
  • BereitgestelltModelOps: Operationen, die Model zur Kommunikation mit Presenter angeboten werden

3. Implementieren von MVP auf Android

Nachdem wir nun eine Vorstellung davon haben, wie die verschiedenen Methoden organisiert werden sollen, können wir mit der Erstellung unserer App beginnen. Wir vereinfachen die Implementierung, indem wir uns nur auf die Aktion konzentrieren, um eine neue Notiz hinzuzufügen. Die Quelldateien dieses Tutorials sind auf GitHub verfügbar.

Wir verwenden nur einen Aktivität mit einem Layout, das Folgendes beinhaltet:

  • Text bearbeiten für neue Notizen
  • Taste eine Notiz hinzufügen
  • RecyclerView alle Notizen auflisten
  • zwei Textvorschau Elemente und a Taste in einem RecyclerView Halter

Schnittstellen

Beginnen wir mit der Erstellung der Schnittstellen. Um alles organisiert zu halten, platzieren wir die Schnittstellen innerhalb eines Inhabers. Auch in diesem Beispiel konzentrieren wir uns auf die Aktion, um eine neue Notiz hinzuzufügen.

public interface MVP_Main / ** * Erforderliche Methoden für Presenter verfügbar. * Eine passive Ebene, die für die Anzeige von Daten verantwortlich ist * und Benutzerinteraktionen empfängt. * / Interface RequiredViewOps // View-Vorgänge, die für Presenter Context zulässig sind. GetAppContext (); Kontext getActivityContext (); void notifyItemInserted (int layoutPosition); void notifyItemRangeChanged (int positionStart, int itemCount);  / ** * Operationen, die View zur Kommunikation mit Presenter angeboten werden. * Verarbeitet Benutzerinteraktionen, sendet Datenanforderungen an Model usw. * / interface ProvidedPresenterOps // Presenter-Vorgänge, für die View void zulässig ist clickNewNote (EditText editText); // Einrichten des Recycler-Adapters int getNotesCount (); NotesViewHolder createViewHolder (ViewGroup-übergeordnetes Element, int viewType); void bindViewHolder (NotesViewHolder-Inhaber, int-Position);  / ** * Erforderliche Presenter-Methoden, die Model zur Verfügung stehen. * / interface RequiredPresenterOps // Presenter-Operationen, die für den Modellkontext zulässig sind getAppContext (); Kontext getActivityContext ();  / ** * Operationen, die Model zur Kommunikation mit Presenter angeboten werden * Behandelt die gesamte Geschäftslogik. * / interface ProvidedModelOps // Für Presenter erlaubte Modelloperationen int getNotesCount (); Beachten Sie getNote (int position); int insertNote (Hinweis beachten); boolean loadData (); 

Ebene anzeigen

Es ist jetzt Zeit, die Ebenen Model, View und Presenter zu erstellen. Schon seit Hauptaktivität wird als View fungieren, sollte das implementieren RequiredViewOps Schnittstelle.

public class MainActivity erweitert AppCompatActivity-Implementierungen View.OnClickListener, MVP_Main.RequiredViewOps private MVP_Main.ProvidedPresenterOps mPresenter; private EditText mTextNewNote; private ListNotes mListAdapter; @Override public void onClick (Ansicht v) switch (v.getId ()) case R.id.fab: // Fügt eine neue Notiz hinzu mPresenter.clickNewNote (mTextNewNote);  @Override public Context getActivityContext () return this;  @Override public Context getAppContext () return getApplicationContext ();  // Benachrichtigen Sie den RecyclerAdapter, dass ein neues Element eingefügt wurde @Override public void notifyItemInserted (int adapterPos) mListAdapter.notifyItemInserted (adapterPos);  // Benachrichtigen Sie den RecyclerAdapter, dass sich Elemente geändert haben @Override public void notifyItemRangeChanged (int positionStart, int itemCount) mListAdapter.notifyItemRangeChanged (positionStart, itemCount);  // Benachrichtigen Sie den RecyclerAdapter, dass sich der Datensatz geändert hat @Override public void notifyDataSetChanged () mListAdapter.notifyDataSetChanged ();  // Recycler-Adapter // Diese Klasse kann einen eigenen Presenter haben, aber der Einfachheit halber wird nur ein Presenter verwendet. // Der Adapter ist passiv und die gesamte Verarbeitung findet in der Presenter-Schicht statt. Die private Klasse ListNotes erweitert RecyclerView.Adapter @Override public int getItemCount () return mPresenter.getNotesCount ();  @Override public NotesViewHolder onCreateViewHolder (ViewGroup-übergeordnetes Element, int viewType) return mPresenter.createViewHolder (übergeordnetes Element, viewType);  @Override public void onBindViewHolder (NotesViewHolder-Inhaber, int-Position) mPresenter.bindViewHolder (Inhaber, Position); 

Präsentationsschicht

Der Presenter ist der Mittelsmann und muss zwei Schnittstellen implementieren:

  • BereitgestelltPresenterOps Anrufe von der Ansicht aus zulassen
  • RequiredPresenterOps um Ergebnisse vom Modell zu erhalten

Achten Sie besonders auf die Referenz der Ansichtsebene. Wir müssen eine verwenden WeakReference schon seit Hauptaktivität jederzeit zerstört werden können und wir wollen Speicherlecks vermeiden. Die Model-Ebene wurde noch nicht eingerichtet. Das machen wir später, wenn wir die MVP-Layer miteinander verbinden.

public class MainPresenter implementiert MVP_Main.ProvidedPresenterOps, MVP_Main.RequiredPresenterOps // View reference. Wir verwenden es als WeakReference //, weil die Aktivität jederzeit zerstört werden kann // und wir keine privaten WeakReference-Speicherlecks erstellen möchten mView; // Modellverweis privat MVP_Main.ProvidedModelOps mModel; / ** * Presenter Constructor * @param view MainActivity * / public MainPresenter (MVP_Main.RequiredViewOps-Ansicht) mView = new WeakReference <> (view);  / ** * Die View-Referenz zurückgeben. * Eine Ausnahme auslösen, wenn die Ansicht nicht verfügbar ist. * / private MVP_Main.RequiredViewOps getView () löst die NullPointerException aus if (mView! = null) return mView.get (); else löst neue NullPointerException aus ("Ansicht ist nicht verfügbar");  / ** * Ruft die Gesamtzahl der Notes aus dem Modell ab. * @Return Notes-Listengröße * / @Override public int getNotesCount () return mModel.getNotesCount ();  / ** * Erstellt den RecyclerView-Halter und richtet seine Ansicht ein. LayoutInflater inflater = LayoutInflater.from (parent.getContext ()); View viewTaskRow = inflater.inflate (R.layout.holder_notes, parent, false); viewHolder = neuer NotesViewHolder (viewTaskRow); return viewHolder;  / ** * Bindet ViewHolder mit RecyclerView * @param-Halter Halter zum Binden von * @param-Position Position auf dem Recycler-Adapter * / @Override public void bindViewHolder (final NotesViewHolder-Halter, int-Position) ; holder.text.setText (note.getText ()); holder.date.setText (note.getDate ()); holder.btnDelete.setOnClickListener (neuer View.OnClickListener () @Override public void onClick (View v) clickDeleteNote (note, holder.getAdapterPosition (), holder.getLayoutPosition ());;  / ** * @return Anwendungskontext * / @Override public Context getAppContext () try return getView (). getAppContext ();  catch (NullPointerException e) return null;  / ** * @return Aktivitätskontext * / @Override public Kontext getActivityContext () try return getView (). getActivityContext ();  catch (NullPointerException e) return null;  / ** * Wird von View aufgerufen, wenn der Benutzer auf die neue Notizschaltfläche klickt. * Erstellt eine Notiz mit vom Benutzer eingegebenem Text und fordert * Model zum Einfügen in die Datenbank auf. * @param editText EditText mit vom Benutzer eingegebenem Text * / @Override public void clickNewNote (final EditText editText) getView (). showProgress (); final String noteText = editText.getText (). toString (); if (! noteText.isEmpty ()) neue AsyncTask() @Override protected Integer doInBackground (Void… params) // fügt eine Notiz in Model ein und gibt die Adapterposition zurück. Return mModel.insertNote (makeNote (noteText));  @Override protected void onPostExecute (Integer adapterPosition) try if (adapterPosition> -1) // Anmerkung eingefügt getView (). ClearEditText (); getView (). notifyItemInserted (adapterPosition + 1); getView (). notifyItemRangeChanged (adapterPosition, mModel.getNotesCount ());  else // Informiert über Fehler getView (). hideProgress (); getView (). showToast (makeToast ("Fehler beim Erstellen der Notiz [" + noteText + "]"));  catch (NullPointerException e) e.printStackTrace ();   .ausführen();  else try getView (). showToast (makeToast ("Leere Notiz kann nicht hinzugefügt werden!"));  catch (NullPointerException e) e.printStackTrace ();  / ** * Erstellt ein Note-Objekt mit angegebenem Text * @param noteText String mit Hinweis-Text * @return A Note-Objekt * / public Hinweis makeNote (String noteText) Note note = new Note (); note.setText (noteText); note.setDate (getDate ()); Rückschein 

Modellebene

Die Modellebene ist für die Handhabung der Geschäftslogik verantwortlich. Es hält eine Anordnungsliste mit den zur Datenbank hinzugefügten Notizen, einer DAO-Referenz zum Durchführen von Datenbankoperationen und einer Referenz zum Presenter.

public class MainModel implementiert MVP_Main.ProvidedModelOps // Presenter-Referenz privat MVP_Main.RequiredPresenterOps mPresenter; privates DAO-mDAO; // Recycler-Daten public ArrayList mNotes; / ** * Hauptkonstruktor, der während des MVP-Setups von Activity aufgerufen wird. * @Param presenter Presenter-Instanz * / public MainModel (MVP_Main.RequiredPresenterOps presenter) this.mPresenter = presenter; mDAO = new DAO (mPresenter.getAppContext ());  / ** * Fügt eine Anmerkung in die DB ein. @Param Note Anmerkung zum Einfügen der Position von * @return Note in ArrayList * / @Override public int insertNote (Hinweis beachten) Hinweis insertNote = mDAO.insertNote (note); if (insertNote! = null) loadData (); return getNotePosition (insertNote);  return -1;  / ** * Lädt alle Daten und holt mit Erfolg Notizen von DB * @return mit Erfolg * * / @Override public boolean loadData () mNotes = mDAO.getAllNotes (); return mNotes! = null;  / ** * Ruft eine bestimmte Notiz aus der Notizenliste unter Verwendung ihrer Arrayposition ab.  / ** * ArrayList-Größe abrufen * @return ArrayList-Größe * / @Override public int getNotesCount () if (mNotes! = Null) return mNotes.size (); 0 zurückgeben; 

4. Alles zusammenbinden

Wenn die MVP-Ebenen vorhanden sind, müssen wir sie instanziieren und die erforderlichen Referenzen einfügen. Bevor wir dies tun, müssen wir uns mit einigen Problemen befassen, die in direktem Zusammenhang mit Android stehen.

Instanziieren der Ebenen

Weil Android die Instantiierung eines Aktivität, Die Ansichtsebene wird für uns instanziiert. Wir sind für die Instanziierung der Presenter- und Model-Ebenen verantwortlich. Leider instanziieren diese Ebenen außerhalb der Aktivität kann problematisch sein.

Es wird empfohlen, eine Abhängigkeitsinjektion zu verwenden, um dies zu erreichen. Da wir uns auf die MVP-Implementierung konzentrieren wollen, werden wir einen einfacheren Ansatz verfolgen. Dies ist nicht der beste Ansatz, der verfügbar ist, aber es ist am einfachsten zu verstehen. Wir werden MVP und Abhängigkeitsinjektion später in dieser Serie besprechen.

  • Präsentieren Sie den Presenter und das Modell in der Aktivität mit lokalen Variablen
  • Konfiguration RequiredViewOps und BereitgestelltModelOps im Presenter
  • Konfiguration RequiredPresenterOps im Modell
  • sparen BereitgestelltPresenterOps als Referenz zur Verwendung in der Ansicht
/ ** * Setup Model View Presenter-Muster * / private void setupMVP () // Den Presenter erstellen MainPresenter presenter = new MainPresenter (this); // Erzeuge das Modell MainModel model = new MainModel (Präsentator); // Setze das Presenter-Modell presenter.setModel (model); // Setze den Presenter als Schnittstelle mPresenter = Presenter; 

Konfigurationsänderungen behandeln

Eine andere Sache, die wir berücksichtigen sollten, ist der Lebenszyklus der Aktivität. Android's Aktivität könnte jederzeit zerstört werden und die Presenter- und Model-Layer könnten damit ebenfalls zerstört werden. Wir müssen dies beheben, indem Sie eine Art Zustandsmaschine verwenden, um den Status während Konfigurationsänderungen zu speichern. Wir sollten auch die anderen Schichten über den Status der Aktivität informieren.

Um dies zu erreichen, verwenden wir eine separate Klasse, StateMaintainer, Dieses enthält ein Fragment, das seinen Status beibehält und dieses Fragment zum Speichern und Abrufen unserer Objekte verwendet. Sie können die Implementierung dieser Klasse in den Quelldateien dieses Tutorials betrachten.

Wir müssen eine hinzufügen onDestroy dem Moderator und dem Modell eine Methode an, um sie über den aktuellen Status der Aktivität zu informieren. Wir müssen auch eine hinzufügen setView Methode an den Presenter, der für den Empfang einer neuen Ansichtsreferenz der neu erstellten Aktivität verantwortlich ist.

public class MainActivity erweitert AppCompatActivity implementiert View.OnClickListener, MVP_Main.RequiredViewOps //… private void setupMVP () // Überprüfen Sie, ob StateMaintainer erstellt wurde, wenn (mStateMaintainer.firstTimeIn ()) // Erzeuge den Presenter MainPresenter-Presenter = new MainPresenter (diese); // Erzeuge das Modell MainModel model = new MainModel (Präsentator); // Setze das Presenter-Modell presenter.setModel (model); // Presenter und Model zu StateMaintainer hinzufügen mStateMaintainer.put (Presenter); mStateMaintainer.put (Modell); // Den Presenter als Schnittstelle festlegen // Um ​​die Kommunikation damit zu begrenzen, mPresenter = Presenter;  // Holen Sie den Presenter von StateMaintainer, sonst // Holen Sie sich den Presenter mPresenter = mStateMaintainer.get (MainPresenter.class.getName ()); // Aktualisierte die Ansicht in Presenter mPresenter.setView (this);  //…

Fazit

Das MVP-Muster kann einige Probleme lösen, die durch die Standardarchitektur von Android verursacht werden. Es macht den Code einfach zu warten und zu testen. Die Einführung von MVP mag auf den ersten Blick schwierig erscheinen, aber sobald Sie die Logik dahinter verstanden haben, ist der gesamte Prozess unkompliziert.

Sie können jetzt Ihre eigene MVP-Bibliothek erstellen oder eine bereits verfügbare Lösung wie Mosby oder simple-mvp verwenden. Sie sollten jetzt besser verstehen, was diese Bibliotheken hinter den Kulissen tun.

Wir sind fast am Ende unserer MVP-Reise. Im dritten und letzten Teil dieser Serie fügen wir Unit-Tests zum Mix hinzu und passen unseren Code an die Abhängigkeitseingabe mit Dagger an. ich hoffe dich dort zu sehen.