Android Architecture Patterns Part 2:Model-View-Presenter
It’s about time we developers start thinking about how we can apply good architecture patterns in our Android apps. Pentru a ajuta la acest lucru, Google oferă planuri de arhitectură Android, unde Erik Hellman și cu mine am lucrat împreună la MVP & eșantion RxJava. Să aruncăm o privire la modul în care am aplicat-o și avantajele și dezavantajele acestei abordări.
iată rolurile fiecărei componente:
- Model — stratul de date. Responsabil pentru gestionarea logicii de afaceri și comunicarea cu straturile de rețea și baze de date.
- View-stratul UI. Afișează datele și notifică prezentatorul despre acțiunile utilizatorului.
- Presenter-preia datele din Model, aplică logica UI și gestionează starea vizualizării, decide ce să afișeze și reacționează la notificările introduse de utilizator din Vizualizare.
deoarece vizualizarea și prezentatorul lucrează strâns împreună, trebuie să aibă o referință unul la altul. Pentru a face unitatea prezentator testable cu JUnit, vizualizarea este abstractizată și o interfață pentru ea folosit. Relația dintre prezentator și vizualizarea corespunzătoare este definită într-o clasă de interfață Contract
, făcând Codul mai lizibil și conexiunea dintre cele două mai ușor de înțeles.
modelul model-view-presenter & RxJava în planurile de arhitectură Android
eșantionul Blueprint este o aplicație „de făcut”. Acesta permite unui utilizator să creeze, să citească, să actualizeze și să șteargă sarcini „de făcut”, precum și să aplice filtre la lista afișată DE SARCINI. RxJava este folosit pentru a muta de pe firul principal și să fie capabil să se ocupe de operații asincrone.
Model
modelul funcționează cu sursele de date la distanță și locale pentru a obține și salva datele. Aici este gestionată logica de afaceri. De exemplu, atunci când solicitați lista Task
s, Modelul ar încerca să le recupereze din sursa de date locală. Dacă este gol, va interoga rețeaua, va salva răspunsul în sursa de date locală și apoi va returna lista.
recuperarea sarcinilor se face cu ajutorul RxJava:
public Observable<List<Task>> getTasks(){
...
}
modelul primește ca parametri în interfețele constructor ale surselor de date locale și la distanță, ceea ce face modelul complet independent de orice clase Android și, astfel, ușor de unitate de testare cu JUnit. De exemplu, pentru a testa căgetTasks
solicită date de la sursa locală, am implementat următorul test:
@Mock
private TasksDataSource mTasksRemoteDataSource;
@Mock
private TasksDataSource mTasksLocalDataSource;
...
@Test
public void getTasks_requestsAllTasksFromLocalDataSource() {
// Given that the local data source has data available
setTasksAvailable(mTasksLocalDataSource, TASKS);
// And the remote data source does not have any data available
setTasksNotAvailable(mTasksRemoteDataSource);
// When tasks are requested from the tasks repository
TestSubscriber<List<Task>> testSubscriber =
new TestSubscriber<>();
mTasksRepository.getTasks().subscribe(testSubscriber); // Then tasks are loaded from the local data source
verify(mTasksLocalDataSource).getTasks();
testSubscriber.assertValue(TASKS);
}
vizualizare
vizualizarea funcționează cu prezentatorul pentru a afișa datele și notifică prezentatorul despre acțiunile utilizatorului. În activitățile MVP, fragmente și vizualizări personalizate Android pot fi vizualizări. Alegerea noastră a fost să folosim fragmente.
toate vizualizările implementează aceeași interfață BaseView care permite setarea unui prezentator.
public interface BaseView<T> {
void setPresenter(T presenter);
}
vizualizarea notifică prezentatorul că este gata să fie actualizat apelând metodasubscribe
a prezentatorului înonResume
. Vizualizarea apeleazăpresenter.unsubscribe()
înonPause
pentru a spune prezentatorului că nu mai este interesat să fie actualizat. Dacă implementarea vizualizării este o vizualizare personalizată Android, atunci metodelesubscribe
șiunsubscribe
trebuie apelate laonAttachedToWindow
șionDetachedFromWindow
. Acțiunile utilizatorului, cum ar fi clicurile pe butoane, vor declanșa metode corespunzătoare în prezentator, acesta fiind cel care decide ce ar trebui să se întâmple în continuare.
vizualizările sunt testate cu Espresso. Ecranul cu statistici, de exemplu, trebuie să afișeze numărul de sarcini active și finalizate. Testul care verifică dacă acest lucru se face corect pune mai întâi unele sarcini în TaskRepository
; apoi lansează StatisticsActivity
și verifică conținutul vizualizărilor:
@Before
public void setup() {
// Given some tasks
TasksRepository.destroyInstance();
TasksRepository repository = Injection.provideTasksRepository( InstrumentationRegistry.getContext()); repository.saveTask(new Task("Title1", "", false));
repository.saveTask(new Task("Title2", "", true)); // Lazily start the Activity from the ActivityTestRule
Intent startIntent = new Intent();
mStatisticsActivityTestRule.launchActivity(startIntent);
}@Test
public void Tasks_ShowsNonEmptyMessage() throws Exception {
// Check that the active and completed tasks text is displayed
Context context = InstrumentationRegistry.getTargetContext();
String expectedActiveTaskText = context
.getString(R.string.statistics_active_tasks);
String expectedCompletedTaskText = context
.getString(R.string.statistics_completed_tasks); onView(withText(containsString(expectedActiveTaskText)))
.check(matches(isDisplayed()));
onView(withText(containsString(expectedCompletedTaskText)))
.check(matches(isDisplayed()));
}
prezentator
prezentatorul și vizualizarea corespunzătoare sunt create de activitate. Referințele la Vizualizare și la TaskRepository
– Modelul – sunt date constructorului prezentatorului. În implementarea constructorului, prezentatorul va apela metodasetPresenter
a vizualizării. Acest lucru poate fi simplificat atunci când se utilizează un cadru de injecție de dependență care permite injectarea prezentatorilor în vizualizările corespunzătoare, reducând cuplarea claselor. Implementarea ToDo-MVP cu pumnal este acoperită într-un alt eșantion.
toți prezentatorii implementează aceeași interfață BasePresenter.
public interface BasePresenter {
void subscribe();
void unsubscribe();
}
când se apelează metodasubscribe
, prezentatorul începe să solicite datele din Model, apoi aplică logica UI datelor primite și le setează la Vizualizare. De exemplu, înStatisticsPresenter
, toate sarcinile sunt solicitate de laTaskRepository
– apoi sarcinile preluate sunt utilizate pentru a calcula numărul de sarcini active și finalizate. Aceste numere vor fi utilizate ca parametri pentrushowStatistics(int numberOfActiveTasks, int numberOfCompletedTasks)
metoda vizualizării.
un test unitar pentru a verifica dacă într-adevăr metodashowStatistics
este apelată cu valorile corecte este ușor de implementat. Ne batem joc deTaskRepository
șiStatisticsContract.View
și dăm obiectele batjocorite ca parametri constructorului unui obiectStatisticsPresenter
. Implementarea testului este:
@Test
public void loadNonEmptyTasksFromRepository_CallViewToDisplay() {
// Given an initialized StatisticsPresenter with
// 1 active and 2 completed tasks
setTasksAvailable(TASKS); // When loading of Tasks is requested
mStatisticsPresenter.subscribe(); // Then the correct data is passed on to the view
verify(mStatisticsView).showStatistics(1, 2);
}
rolul metodeiunsubscribe
este de a șterge toate abonamentele prezentatorului, evitând astfel scurgerile de memorie.
în afară desubscribe
șiunsubscribe
, fiecare prezentator expune alte metode, corespunzătoare acțiunilor utilizatorului din Vizualizare. De exemplu, AddEditTaskPresenter
, adaugă metode precum createTask
, care ar fi apelat atunci când utilizatorul apasă butonul care creează o nouă sarcină. Acest lucru asigură că toate acțiunile utilizatorului – și, în consecință, toată logica UI-trec prin prezentator și, prin urmare, pot fi testate unitar.
dezavantajele modelului Model-Vizualizare-prezentator
modelul Model-Vizualizare-prezentator aduce cu sine o separare foarte bună a preocupărilor. Deși acesta este cu siguranță un profesionist, atunci când dezvoltați o aplicație mică sau un prototip, acest lucru poate părea o regie. Pentru a reduce numărul de interfețe utilizate, unii dezvoltatori elimină clasa de interfațăContract
și interfața pentru prezentator.
una dintre capcanele MVP apare atunci când se deplasează logica UI la Prezentator: aceasta devine acum o clasă atotștiutoare, cu mii de linii de cod. Pentru a rezolva acest lucru, împărțiți codul și mai mult și nu uitați să creați clase care au o singură responsabilitate și sunt testabile unitar.
concluzie
modelul Model-vizualizare-controler are două dezavantaje principale: în primul rând, vizualizarea are o referință atât la controler, cât și la Model; și în al doilea rând, nu limitează manipularea logicii UI la o singură clasă, această responsabilitate fiind împărțită între controler și vizualizare sau Model. Modelul Model-View-Presenter rezolvă ambele probleme prin ruperea conexiunii pe care Vizualizarea o are cu modelul și crearea unei singure clase care gestionează tot ceea ce ține de prezentarea vizualizării — prezentatorul: o singură clasă ușor de testat.
Ce se întâmplă dacă vrem o arhitectură bazată pe evenimente, în care Vizualizarea reacționează la schimbări? Rămâneți la curent cu următoarele modele eșantionate în planurile de arhitectură Android pentru a vedea cum poate fi implementat acest lucru. Până atunci, citiți despre implementarea modelului Model-View-ViewModel în aplicația upday.