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. For at hjælpe med dette tilbyder Google Android Architecture Blueprints, hvor Erik Hellman og jeg arbejdede sammen om MVP & rksjava prøve. Lad os se på, hvordan vi anvendte det og fordele og ulemper ved denne tilgang.
Her er rollerne for hver komponent:
- Model — datalaget. Ansvarlig for håndtering af forretningslogik og kommunikation med netværks-og databaselagene.
- Vis-UI-laget. Viser dataene og giver præsentanten besked om brugerhandlinger.
- Presenter-henter dataene fra modellen, anvender UI-logikken og styrer visningens tilstand, beslutter, hvad der skal vises og reagerer på brugerinputmeddelelser fra visningen.
da visningen og præsentanten arbejder tæt sammen, skal de have en henvisning til hinanden. For at gøre Præsentationsenheden testbar med JUnit, er visningen abstraheret og en grænseflade til den brugt. Forholdet mellem præsentanten og dens tilsvarende visning er defineret i en Contract
interfaceklasse, hvilket gør koden mere læsbar og forbindelsen mellem de to lettere at forstå.
modellen-vis-presenter mønster & rksjava i Android arkitektur blueprints
Blueprint prøve er en “at gøre” ansøgning. Det lader en bruger oprette, læse, opdatere og slette “at gøre” opgaver, samt anvende filtre til den viste liste over opgaver. Rksjava bruges til at bevæge sig væk fra hovedtråden og være i stand til at håndtere asynkrone operationer.
Model
modellen arbejder med eksterne og lokale datakilder for at hente og gemme dataene. Det er her forretningslogikken håndteres. For eksempel, når du anmoder om listen over Task
s, Ville modellen forsøge at hente dem fra den lokale datakilde. Hvis det er tomt, spørger det netværket, gemmer svaret i den lokale datakilde og returnerer derefter listen.
hentning af opgaver udføres ved hjælp af Rksjava:
public Observable<List<Task>> getTasks(){
...
}
modellen modtager som parametre i konstruktørgrænsefladerne for de lokale og eksterne datakilder, hvilket gør modellen helt uafhængig af alle Android-klasser og dermed let at enhedstest med JUnit. For eksempel for at teste, at getTasks
anmoder om data fra den lokale kilde, implementerede vi følgende 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);
}
Vis
visningen fungerer sammen med præsentanten for at vise dataene, og den underretter præsentanten om brugerens handlinger. I MVP-aktiviteter kan fragmenter og brugerdefinerede Android-visninger være visninger. Vores valg var at bruge fragmenter.
alle visninger implementerer den samme basevisningsgrænseflade, der gør det muligt at indstille en præsentator.
public interface BaseView<T> {
void setPresenter(T presenter);
}
visningen meddeler præsentanten, at den er klar til at blive opdateret ved at kaldesubscribe
præsentantens metode ionResume
. Visningen kalder presenter.unsubscribe()
i onPause
for at fortælle præsentanten, at den ikke længere er interesseret i at blive opdateret. Hvis implementeringen af visningen er en Android brugerdefineret visning, skal subscribe
og unsubscribe
metoderne kaldes onAttachedToWindow
og onDetachedFromWindow
. Brugerhandlinger, som knapklik, udløser tilsvarende metoder i præsentationsværten, dette er den, der bestemmer, hvad der skal ske næste gang.
visningerne testes med Espresso. Statistikskærmen skal for eksempel vise antallet af aktive og udførte opgaver. Testen, der kontrollerer, at dette er gjort korrekt, sætter først nogle opgaver i TaskRepository
; starter derefter StatisticsActivity
og kontrollerer indholdet af visningerne:
@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()));
}
Presenter
Presenter og dens tilsvarende visning oprettes af aktiviteten. Henvisninger til visningen og til TaskRepository
– modellen – gives til præsentantens konstruktør. I implementeringen af konstruktøren kalder præsentanten setPresenter
visningsmetoden. Dette kan forenkles, når du bruger en afhængighedsinjektionsramme, der tillader injektion af præsentanterne i de tilsvarende visninger, hvilket reducerer koblingen af klasserne. Implementeringen af ToDo-MVP med dolk er dækket af en anden prøve.
alle præsentanter implementerer den samme BasePresenter-grænseflade.
public interface BasePresenter {
void subscribe();
void unsubscribe();
}
nårsubscribe
– metoden kaldes, begynder præsentanten at anmode om dataene fra modellen, så anvender den UI-logikken til de modtagne data og indstiller den til visningen. For eksempel i StatisticsPresenter
anmodes alle opgaver fra TaskRepository
– så bruges de hentede opgaver til at beregne antallet af aktive og afsluttede opgaver. Disse tal vil blive brugt som parametre for showStatistics(int numberOfActiveTasks, int numberOfCompletedTasks)
visningsmetoden.
en enhedstest for at kontrollere, at showStatistics
metoden kaldes med de korrekte værdier er let at implementere. Vi håner TaskRepository
og StatisticsContract.View
og giver de hånede objekter som parametre til konstruktøren af et StatisticsPresenter
objekt. Testimplementeringen er:
@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);
}
rollen afunsubscribe
metoden er at rydde alle abonnementer på præsentanten og dermed undgå hukommelseslækager.
bortset fra subscribe
og unsubscribe
udsætter hver præsentator andre metoder, der svarer til brugerhandlingerne i visningen. For eksempel tilføjer AddEditTaskPresenter
metoder som createTask
, der ville blive kaldt, når brugeren trykker på knappen, der opretter en ny opgave. Dette sikrer, at alle brugerhandlinger – og dermed al UI – logikken-går gennem præsentanten og derved kan enhedstestes.
ulemper ved Model-Vis-Præsentationsmønster
model-Vis-Præsentationsmønsteret medfører en meget god adskillelse af bekymringer. Selvom dette helt sikkert er en professionel, når du udvikler en lille app eller en prototype, kan dette virke som en overhead. For at reducere antallet af anvendte grænseflader fjerner nogle udviklere Contract
interfaceklasse og grænsefladen til præsentanten.
en af faldgruberne i MVP vises, når du flytter UI-logikken til præsentanten: dette bliver nu en alvidende klasse med tusinder af kodelinjer. For at løse dette skal du opdele koden endnu mere og huske at oprette klasser, der kun har et ansvar og er enhedstestbare.
konklusion
Model-Vis-Controller-mønsteret har to hoved ulemper: for det første har visningen en henvisning til både controlleren og modellen; og for det andet begrænser det ikke håndteringen af UI-logik til en enkelt klasse, idet dette ansvar deles mellem controlleren og visningen eller modellen. Model-Vis-Præsentationsmønsteret løser begge disse problemer ved at bryde forbindelsen, som Visningen har med modellen, og kun oprette en klasse, der håndterer alt relateret til præsentationen af visningen — præsentanten: en enkelt klasse, der er let at enhedstest.
Hvad hvis vi ønsker en begivenhedsbaseret arkitektur, hvor visningen reagerer på ændringer? Hold øje med de næste mønstre, der er samplet i Android Architecture Blueprints for at se, hvordan dette kan implementeres. Indtil da kan du læse om vores implementering af Model-Vis-Visningsmønster i upday-appen.