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:
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.
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.
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:
Hinweis
Objekt mit dem vom Benutzer eingegebenen Text und fordert das Modell auf, ihn in die Datenbank einzufügen.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ängeBereitgestelltPresenterOps
: Operationen, die View zur Kommunikation mit Presenter angeboten werdenRequiredPresenterOps
: Erforderliche Presenter-Vorgänge, die Model zur Verfügung stehenBereitgestelltModelOps
: Operationen, die Model zur Kommunikation mit Presenter angeboten werdenNachdem 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 NotizenTaste
eine Notiz hinzufügenRecyclerView
alle Notizen auflistenTextvorschau
Elemente und a Taste
in einem RecyclerView
HalterBeginnen 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 ();
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);
Der Presenter ist der Mittelsmann und muss zwei Schnittstellen implementieren:
BereitgestelltPresenterOps
Anrufe von der Ansicht aus zulassenRequiredPresenterOps
um Ergebnisse vom Modell zu erhaltenAchten 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öchtenmView; // 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
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 ArrayListmNotes; / ** * 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;
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.
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.
RequiredViewOps
und BereitgestelltModelOps
im PresenterRequiredPresenterOps
im ModellBereitgestelltPresenterOps
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;
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); //…
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.