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. För att hjälpa till med detta erbjuder Google Android-arkitekturritningar, där Erik Hellman och jag arbetade tillsammans på MVP & RxJava-provet. Låt oss ta en titt på hur vi tillämpade det och fördelarna och nackdelarna med detta tillvägagångssätt.
här är rollerna för varje komponent:
- Modell — datalagret. Ansvarig för hantering av affärslogik och kommunikation med nätverks-och databaslagren.
- Visa-UI-lagret. Visar data och meddelar presentatören om användaråtgärder.
- presentatör-hämtar data från modellen, tillämpar UI-logiken och hanterar vyens tillstånd, bestämmer vad som ska visas och reagerar på användarinmatningsmeddelanden från vyn.
eftersom vyn och presentatören arbetar nära varandra måste de ha en hänvisning till varandra. För att göra Presentatörsenheten testbar med JUnit abstraheras vyn och ett gränssnitt för den används. Förhållandet mellan presentatören och dess motsvarande vy definieras i en Contract
gränssnittsklass, vilket gör koden mer läsbar och anslutningen mellan de två lättare att förstå.
model-view-presentatör mönster&RxJava i Android arkitektur ritningar
Blueprint provet är en ”att göra” ansökan. Det låter en användare skapa, läsa, uppdatera och ta bort ”att göra” uppgifter, samt tillämpa filter på den visade listan över uppgifter. RxJava används för att flytta bort huvudgängan och kunna hantera asynkrona operationer.
Modell
modellen fungerar med fjärr-och lokala datakällor för att hämta och spara data. Det är här affärslogiken hanteras. När du till exempel begär listan över Task
s, skulle modellen försöka hämta dem från den lokala datakällan. Om det är tomt kommer det att fråga nätverket, spara svaret i den lokala datakällan och sedan returnera listan.
hämtningen av uppgifter görs med hjälp av RxJava:
public Observable<List<Task>> getTasks(){
...
}
modellen får som parametrar i konstruktörgränssnittet för de lokala och avlägsna datakällorna, vilket gör modellen helt oberoende av alla Android-klasser och därmed lätt att enhetstest med JUnit. Till exempel, för att testa att getTasks
begär data från den lokala källan, genomförde vi följande 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);
}
Visa
vyn fungerar med presentatören för att visa data och det meddelar presentatören om användarens åtgärder. I MVP-aktiviteter kan fragment och anpassade Android-vyer vara vyer. Vårt val var att använda fragment.
alla vyer implementerar samma BaseView-gränssnitt som gör det möjligt att ställa in en presentatör.
public interface BaseView<T> {
void setPresenter(T presenter);
}
vyn meddelar presentatören att den är redo att uppdateras genom att ringasubscribe
presentatörens metod ionResume
. Vyn anropar presenter.unsubscribe()
I onPause
för att berätta för presentatören att den inte längre är intresserad av att uppdateras. Om implementeringen av vyn är en Android-anpassad vy, måstesubscribe
ochunsubscribe
metoder anropasonAttachedToWindow
ochonDetachedFromWindow
. Användaråtgärder, som knappklick, utlöser motsvarande metoder i presentatören, det här är den som bestämmer vad som ska hända nästa.
vyerna testas med Espresso. Statistikskärmen måste till exempel visa antalet aktiva och slutförda uppgifter. Testet som kontrollerar att detta görs korrekt sätter först några uppgifter i TaskRepository
; startar sedan StatisticsActivity
och kontrollerar innehållet i vyerna:
@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()));
}
presentatör
presentatören och dess motsvarande vy skapas av aktiviteten. Hänvisningar till vyn och till TaskRepository
– modellen – ges till presentatörens konstruktör. Vid genomförandet av konstruktören kommer presentatören att ringa setPresenter
– metoden för vyn. Detta kan förenklas när man använder ett ramverk för beroendeinjektion som möjliggör injektion av presentatörerna i motsvarande vyer, vilket minskar kopplingen av klasserna. Genomförandet av ToDo-MVP med dolk omfattas av ett annat prov.
alla presentatörer implementerar samma BasePresenter-gränssnitt.
public interface BasePresenter {
void subscribe();
void unsubscribe();
}
Närsubscribe
– metoden kallas börjar presentatören begära data från modellen, då tillämpar den UI-logiken på den mottagna data och ställer in den i vyn. Till exempel, i StatisticsPresenter
, begärs alla uppgifter från TaskRepository
– då används de hämtade uppgifterna för att beräkna antalet aktiva och slutförda uppgifter. Dessa siffror kommer att användas som parametrar för showStatistics(int numberOfActiveTasks, int numberOfCompletedTasks)
metod för vyn.
ett enhetstest för att kontrollera attshowStatistics
– metoden anropas med rätt värden är lätt att implementera. Vi hånar TaskRepository
och StatisticsContract.View
och ger de hånade objekten som parametrar till konstruktören av ett StatisticsPresenter
– objekt. Testimplementeringen är:
@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 förunsubscribe
– metoden är att rensa alla prenumerationer på presentatören och därmed undvika minnesläckor.
bortsett från subscribe
och unsubscribe
, exponerar varje presentatör andra metoder som motsvarar användaråtgärderna i vyn. Till exempel lägger AddEditTaskPresenter
till metoder som createTask
, som skulle kallas när användaren trycker på knappen som skapar en ny uppgift. Detta säkerställer att alla användaråtgärder – och därmed all UI – logik-går igenom presentatören och därmed kan enhetstestas.
nackdelar med Model-View-presentatör mönster
Model-View-presentatör mönster för med sig en mycket god separation av oro. Även om detta är säkert ett proffs, när man utvecklar en liten app eller en prototyp, detta kan verka som en overhead. För att minska antalet gränssnitt som används tar vissa utvecklare bort gränssnittsklassen Contract
och gränssnittet för presentatören.
en av fallgroparna i MVP visas när du flyttar UI-logiken till presentatören: detta blir nu en allvetande klass med tusentals kodrader. För att lösa detta, dela upp koden ännu mer och kom ihåg att skapa klasser som bara har ett ansvar och är enhetstestbara.
slutsats
modellen-View-Controller mönstret har två huvudsakliga nackdelar: för det första har vyn en hänvisning till både styrenheten och modellen; och för det andra begränsar det inte hanteringen av UI-logik till en enda klass, detta ansvar delas mellan styrenheten och vyn eller modellen. Modellen-visa-Presentatörsmönstret löser båda dessa problem genom att bryta anslutningen som Vyn har med modellen och skapa bara en klass som hanterar allt relaterat till presentationen av vyn — presentatören: en enda klass som är lätt att enhetstesta.
vad händer om vi vill ha en händelsebaserad arkitektur, där vyn reagerar på förändringar? Håll ögonen öppna för nästa mönster samplade i Android arkitektur ritningar för att se hur detta kan genomföras. Fram till dess, läs om vår modell-View-ViewModel-mönsterimplementering i upday-appen.