I den forrige veiledningen snakket vi om Model View Presenter-mønsteret, hvordan det brukes på Android, og hva de viktigste fordelene er. I denne opplæringen undersøker vi Model View Presenter-mønsteret mer detaljert ved å implementere det i en Android-applikasjon.
I denne opplæringen:
Model View Presenter-mønsteret er et arkitektonisk mønster basert på modellvisningskontrollmønsteret (MVC) som øker adskillelsen av bekymringer og letter enhetstesting. Det skaper tre lag, Modell, Utsikt, og Presenter, hver med et veldefinert ansvar.
Modellen holder forretningslogikken til søknaden. Den styrer hvordan data opprettes, lagres og endres. Visningen er et passivt grensesnitt som viser data og ruter brukerhandlinger til presentatøren. Presentatøren fungerer som mellommann. Den henter data fra modellen og viser den i visningen. Den behandler også brukerhandlinger videresendt av visningen.
Vi skal bygge en enkel notater søknad for å illustrere MVP. Appen lar brukeren ta notater, lagre dem i en lokal database og slette notater. For å gjøre det enkelt, har appen bare én aktivitet.
I denne opplæringen konsentrerer vi seg primært om implementeringen av MVP-mønsteret. Andre funksjoner, for eksempel å sette opp en SQLite-database, bygge en DAO, eller håndtere brukerinteraksjon, hoppes over. Hvis du trenger hjelp med noen av disse emnene, har Envato Tuts + noen gode opplæringsprogrammer om disse emnene.
La oss begynne med opprettelsen av et nytt notat. Hvis vi bryter denne handlingen inn i mindre operasjoner, så er dette hva strømmen ser ut som å bruke MVP arkitektonisk mønster:
Merk
objekt med teksten som er oppgitt av brukeren og ber Modellen å sette den inn i databasen.La oss nå vurdere operasjonene som trengs for å oppnå denne handlingen, og skille dem fra MVP. For å holde de forskjellige objektene løst koblet, tar kommunikasjonen mellom lagene sted ved å bruke grensesnitt. Vi trenger fire grensesnitt:
RequiredViewOps
: obligatorisk Se operasjoner tilgjengelig for PresenterProvidedPresenterOps
: operasjoner tilbudt til visning for kommunikasjon med presentatørRequiredPresenterOps
: Påkrevd Presentasjonsoperasjoner tilgjengelig for ModellProvidedModelOps
: Operasjoner som tilbys til Model for å kommunisere med PresenterNå som vi har en ide om hvordan de ulike metodene skal organiseres, kan vi begynne å lage vår app. Vi forenkler implementeringen ved bare å fokusere på handlingen for å legge til et nytt notat. Kildefilene til denne opplæringen er tilgjengelige på GitHub.
Vi bruker bare en Aktivitet
med et oppsett som inkluderer:
EditText
for nye notaterKnapp
å legge til et notatRecyclerView
å liste alle notaterTextView
elementer og a Knapp
inne i a RecyclerView
holderLa oss begynne med å skape grensesnittene. For å holde alt organisert, plasserer vi grensesnittene i en holder. Igjen, i dette eksempelet fokuserer vi på handlingen for å legge til et nytt notat.
offentlig grensesnitt MVP_Main / ** * Obligatorisk visningsmetoder tilgjengelig for Presenter. * Et passivt lag, ansvarlig for å vise data * og motta brukerinteraksjoner * / grensesnitt RequiredViewOps // Se operasjoner tillatt til Presenter Context getAppContext (); Kontekst getActivityContext (); void notifyItemInserted (int layoutPosition); void notifyItemRangeChanged (int posisjonStart, int itemCount); / ** * Operasjoner som tilbys til Vis for å kommunisere med Presenter. * Behandler brukerinteraksjoner, sender dataforespørsler til modell, etc. * / grensesnitt ProvidedPresenterOps // Presenter operasjoner tillatt å vise ugyldig clickNewNote (EditText editText); // sette opp resirkuleringsadapter int getNotesCount (); NotesViewHolder createViewHolder (ViewGroup foreldre, int viewType); void bindViewHolder (NotesViewHolder holder, int posisjon); / ** * Påkrevd Presentasjonsmetoder tilgjengelig for Model. * / grensesnitt RequiredPresenterOps // Presenter operasjoner tillatt til Model Context getAppContext (); Kontekst getActivityContext (); / ** * Operasjoner som tilbys til Model for å kommunisere med Presenter * Håndterer all data business logic. * / grensesnitt ProvidedModelOps // Modelloperasjoner tillatt til Presenter int getNotesCount (); Merk getNote (int posisjon); int insertNote (Notat notat); boolsk loadData ();
Det er nå på tide å lage modell, visning og presentasjonslag. Siden Hoved aktivitet
vil fungere som Vis, den bør implementere RequiredViewOps
grensesnitt.
offentlig klasse MainActivity utvider AppCompatActivity implementerer View.OnClickListener, MVP_Main.RequiredViewOps private MVP_Main.ProvidedPresenterOps mPresenter; privat EditText mTextNewNote; privat ListNotes mListAdapter; @Override public void onClick (Vis v) switch (v.getId ()) tilfelle R.id.fab: // legger til et nytt notat mPresenter.clickNewNote (mTextNewNote); @ Overstyr offentlig kontekst getActivityContext () returner dette; @ Overstyr offentlig kontekst getAppContext () return getApplicationContext (); // Gi beskjed om RecyclerAdapter som et nytt element ble satt inn @ Overstyr offentleg tomt informerItemInserted (int adapterPos) mListAdapter.notifyItemInserted (adapterPos); // varsle RecyclerAdapter at elementene har endret @Override public void notifyItemRangeChanged (int posisjonStart, int itemCount) mListAdapter.notifyItemRangeChanged (posisjonStart, itemCount); // varsle RecyclerAdapter at datasettet er endret @Override public void notifyDataSetChanged () mListAdapter.notifyDataSetChanged (); // Recycler adapter // Denne klassen kan ha sin egen Presenter, men for enkelhets skyld skyldes det bare en Presenter. // Adapteren er passiv og all behandling skjer // i Presenter-laget. Private klasse ListNotes utvider RecyclerView.Adapter@Override public int getItemCount () return mPresenter.getNotesCount (); @ Overstyr offentlige notaterViewHolder onCreateViewHolder (ViewGroup foreldre, int viewType) return mPresenter.createViewHolder (foreldre, viewType); @Override public void onBindViewHolder (NotesViewHolder holder, int posisjon) mPresenter.bindViewHolder (holder, posisjon);
Presentatøren er mellommann og trenger å implementere to grensesnitt:
ProvidedPresenterOps
for å tillate anrop fra visningenRequiredPresenterOps
å motta resultater fra modellenVær spesielt oppmerksom på View-lagreferansen. Vi må bruke en WeakReference
siden Hoved aktivitet
kan bli ødelagt når som helst, og vi vil unngå minnelekkasje. Modelllagret har heller ikke blitt satt opp ennå. Vi gjør det senere når vi kobler sammen MVP-lagene sammen.
offentlig klasse MainPresenter implementerer MVP_Main.ProvidedPresenterOps, MVP_Main.RequiredPresenterOps // Se referanse. Vi bruker som WeakReference // fordi aktiviteten kan ødelegges når som helst // og vi ønsker ikke å lage en hukommelseskap privat WeakReferencemVIS; // Modell referanse privat MVP_Main.ProvidedModelOps mMmodel; / ** * Presenter Constructor * @param view MainActivity * / public MainPresenter (MVP_Main.RequiredViewOps visning) mView = new WeakReference <> (visning); / ** * Returner visningsreferansen. * Kast et unntak hvis visningen ikke er tilgjengelig. * / privat MVP_Main.RequiredViewOps getView () kaster NullPointerException hvis (mView! = null) returner mView.get (); ellers kaster du ny NullPointerException ("Vis er utilgjengelig"); / ** * Henter totalt Notes teller fra Model * @return Notater listestørrelse * / @Override public int getNotesCount () return mModel.getNotesCount (); / ** * Oppretter RecyclerView-holderen og konfigurerer sin visning * @param foreldre Recycler viewGroup * @param viewType Holder type * @return Recycler ViewHolder * / @Override public NotesViewHolder createViewHolder (ViewGroup foreldre, int viewType) NotesViewHolder viewHolder; LayoutInflater inflater = LayoutInflater.from (parent.getContext ()); View viewTaskRow = inflater.inflate (R.layout.holder_notes, foreldre, false); viewHolder = nye NotesViewHolder (viewTaskRow); returnere viewHolder; / ** * Binder ViewHolder med RecyclerView * @param holder Holder for å binde * @param posisjon Plassering på Recycler adapter * / @Override public void bindViewHolder (endelig NotesViewHolder holder, int posisjon) endelig Note note = mModel.getNote (posisjon) ; holder.text.setText (note.getText ()); holder.date.setText (note.getDate ()); holder.btnDelete.setOnClickListener (new View.OnClickListener () @Override public void onClick (Vis v) clickDeleteNote (notat, holder.getAdapterPosition (), holder.getLayoutPosition ());); / ** * @return Applikasjonskontekst * / @Override public Context getAppContext () prøv return getView (). getAppContext (); fangst (NullPointerException e) return null; / ** * @return Aktivitetskontekst * / @Override public Context getActivityContext () prøv return getView (). getActivityContext (); fangst (NullPointerException e) return null; / ** * Kalt av Vis når bruker klikker på ny Notat-knapp. * Oppretter en notat med tekst skrevet av brukeren og spør * Modell for å sette den inn i DB. * @param editText EditText med tekst skrevet av brukeren * / @Override public void clickNewNote (endelig EditText editText) getView (). showProgress (); siste String noteText = editText.getText (). toString (); hvis (! noteText.isEmpty ()) ny AsyncTask () @Overtrid beskyttet Integer doInBackground (Void ... params) // Innsetter notat i Modell, returadapterposisjon returnerer mModel.insertNote (makeNote (noteText)); @Override protected void onPostExecute (Integer adapterPosition) prøv hvis (adapterPosition> -1) // Merk innført getView (). ClearEditText (); getView (). notifyItemInserted (adapterposisjon + 1); getView (). notifyItemRangeChanged (adapterposisjon, mModel.getNotesCount ()); else // Informerer om feil getView (). hideProgress (); getView (). showToast (makeToast ("Feil opprette notat [" + noteText + "]")); fangst (NullPointerException e) e.printStackTrace (); .henrette(); ellers prøv getView (). showToast (makeToast ("Kan ikke legge til et tomt notat!")); fangst (NullPointerException e) e.printStackTrace (); / ** * Oppretter et Objektobjekt med gitt tekst * @param noteText String med Notattekst * @return En Objektobjekt * / offentlig Notatnotat (Stringnotatekst) Notat notat = Ny Notat (); note.setText (noteText); note.setDate (getDate ()); retur notat;
Modelllaget er ansvarlig for håndtering av forretningslogikken. Den har en Arraylist
med notatene lagt til i databasen, en DAO-referanse for å utføre databaseoperasjoner, og en referanse til presentatøren.
offentlig klasse MainModel implementerer MVP_Main.ProvidedModelOps // Presenter referanse privat MVP_Main.RequiredPresenterOps mPresenter; privat DAO mDAO; // Recycler data offentlig ArrayListmNotes; / ** * Hovedkonstruent, kalt Aktivitet under MVP-oppsett * @param presentator Presenter instance * / public MainModel (MVP_Main.RequiredPresenterOps presentator) this.mPresenter = presenterer; mDAO = ny DAO (mPresenter.getAppContext ()); / ** * Setter inn et notat på DB * @param notat Merk for å sette inn * @return Obs posisjon på ArrayList * / @Override public int insertNote (Notat) Note insertedNote = mDAO.insertNote (note); hvis (insertedNote! = null) loadData (); returnere getNotePosition (insertedNote); return -1; / ** * Laster alle data, får notater fra DB * @return sant med suksess * / @Override offentlig boolean loadData () mNotes = mDAO.getAllNotes (); returner mNotes! = null; / ** * Får et bestemt notat fra notatlisten ved hjelp av sin array posisjon * @param posisjon Array posisjon * @return Notat fra listen * / @Override offentlig Note getNote (int posisjon) return mNotes.get (posisjon); / ** * Få ArrayList størrelse * @return ArrayList størrelse * / @Override public int getNotesCount () hvis (mNotes! = Null) returner mNotes.size (); returner 0;
Med MVP-lagene på plass, må vi instantiere dem og sette inn de nødvendige referansene. Før vi gjør det, må vi ta opp noen problemer som er direkte relatert til Android.
Fordi Android ikke tillater instantiering av en Aktivitet
, Visningslaget vil bli instantiated for oss. Vi er ansvarlige for å instantiere presentasjons- og modelllagene. Dessverre instantiating disse lagene utenfor Aktivitet
kan være problematisk.
Det anbefales å bruke en form for beredskapsinjeksjon for å oppnå dette. Siden vårt mål er å konsentrere seg om MVP-implementeringen, vil vi ta en enklere tilnærming. Dette er ikke den beste tilnærmingen som er tilgjengelig, men det er lettest å forstå. Vi diskuterer MVP og avhengighetsinjeksjon senere i denne serien.
RequiredViewOps
og ProvidedModelOps
i presentatørenRequiredPresenterOps
i modellenProvidedPresenterOps
som referanse til bruk i visningen/ ** * Setup Model View Presenter mønster * / private void setupMVP () // Opprett Presenter MainPresenter presenterer = ny MainPresenter (dette); // Opprett Model MainModel-modellen = ny MainModel (presentator); // Sett Presenter modell presenterer. SetModel (modell); // Sett presentatøren som et grensesnitt mPresenter = presentator;
En annen ting vi bør vurdere er aktivitetens livssyklus. Android Aktivitet
kan ødelegges når som helst, og presentasjons- og modelllagene kan også bli ødelagt med det. Vi må fikse dette ved å bruke en slags statlig maskin for å lagre tilstand under konfigurasjonsendringer. Vi bør også informere de andre lagene om aktivitetens tilstand.
For å oppnå dette, bruker vi en egen klasse, StateMaintainer
, som inneholder et fragment som opprettholder sin tilstand og bruker dette fragmentet til å lagre og hente gjenstandene våre. Du kan se på implementeringen av denne klassen i kildefilene i denne opplæringen.
Vi må legge til en onDestroy
metode til presentatøren og modellen for å informere dem om aktivitetens nåværende tilstand. Vi må også legge til en setview
metode til presentatøren, som vil være ansvarlig for å motta en ny visning av den gjenopprettede aktiviteten.
offentlig klasse MainActivity utvider AppCompatActivity implementerer View.OnClickListener, MVP_Main.RequiredViewOps // ... private void setupMVP () // Sjekk om StateMaintainer er opprettet hvis (mStateMaintainer.firstTimeIn ()) // Opprett presentatøren MainPresenter presenterer = ny MainPresenter (dette); // Opprett Model MainModel-modellen = ny MainModel (presentator); // Sett Presenter modell presenterer. SetModel (modell); // Legg til presentator og modell til StateMaintainer mStateMaintainer.put (presentator); mStateMaintainer.put (modell); // Sett presentatøren som et grensesnitt // For å begrense kommunikasjonen med den mPresenter = presentatør; // få presentatøren fra StateMaintainer annet // Få presentatøren mPresenter = mStateMaintainer.get (MainPresenter.class.getName ()); // Oppdatert visning i Presenter mPresenter.setView (dette); // ...
MVP-mønsteret er i stand til å løse noen problemer forårsaket av Android standardarkitektur. Det gjør at koden din er lett å vedlikeholde og teste. Å vedta MVP kan kanskje se vanskelig ut først, men når du forstår logikken bak den, er hele prosessen enkel.
Du kan nå lage ditt eget MVP-bibliotek eller bruke en løsning som allerede er tilgjengelig, for eksempel Mosby eller simple-mvp. Du bør nå bedre forstå hva disse bibliotekene gjør bak kulissene.
Vi er nesten på slutten av vår MVP-reise. I den tredje og siste delen av denne serien legger vi til enhetstesting i blandingen og tilpasser koden vår for å bruke avhengighetsinjeksjon med Dagger. Jeg håper å se deg der.