提交 2e20b4a5 编写于 作者: F Florina Muntenescu 提交者: GitHub

MVVM + RxJava (#284)

* Statistics feature implemented based on MVVM pattern.
missing: 1. tests for view model 2. removal of presenter 3. gradle update.

* StatisticsViewModel tests added

* Statistics updated based on code reviewes

* Removing unused classes.
Small code cleanup.

* Using Action, instead of subscribe.

* Order of unbind/super.onPause changed

* Making sure the progress is updated also in case of error

* App compiles on prod version.
Small code review fixes

* Created a resource provider.
Moved the logic of the statistics display in the viewmodel.

* Tests fixed

* Allowing formatted text.

* Typo fixed

* Removing unnecessary method.

* lambda expressions used with Statistics.

* StatisticsViewModel tests added

* Task detail implemented using MVVM pattern.

* Removed unnecessary check for null

* Returning preconditions.

* Moving unbind before super.onPause

* Using Single and Completable.

* Using fromAction instead of fromCallable

* Throwing RuntimeException and making a member in the tests static.

* Tests updated after exception type changed

* App compiles on prod version.
Small code review fixes

* Members visibility changed

* Making the app compile for prod version also

* Navigation provider added

* TaskDetail implemented with MVVM

* TaskDetailFragment add Subscriptions to the CompositeSubscription

* Unused classes removed

* Updated the injection for the prod flavor

* lambda expressions used

* Add Edit task view model created

* more tests added for the AddEditTaskViewModel

* Javadoc added for class

* TasksViewModel added

* Added more of the tasks list functionality

* Tests added

* More tests added.
Unused classes removed

* more tests added

* some logs added for debugging

* ViewHolder pattern used

* saving the state for the tasks in the bundle

* save the state for add/edit task

* Tests added for the state restoring

* Removed unused classes, added strict mode

* Moving work off the UI thread

* Comment updated

* Cleanup

* Fixing tests and cleaning up

* Creating one model for the entire tasks list

* Having the TaskDetail using one model

* Log removed

* Moved the binding/unbinding to onResume/onPause (#14)

* Moved the binding/unbinding to onResume/onPause
Moved more navigation handling to ViewModel

* Order of method calls changed

* Code review fixes

* Creating a navigator for the tasks screen

* New lines removed

* Added navigators for task detail and tasks

* Added navigator for add edit task feature

* Imports added

* Reactive Repository Refactoring (#15)

* Reactive Repository Refactoring

* Fixes from PR review

* Cosmetic fixes and method renaming.

* Moving the schedulerprovider in the constructor.

* Revert "Moving the schedulerprovider in the constructor."

This reverts commit 64cfc75fcc0a40aa1f2b23571534fe93bd1e8197.

* Revert "Reactive Repository Refactoring (#15)"

This reverts commit a64a041a6349b8d1c00e0c8100d697e8e5d59fed.

* Making the repository reactive (#17)

* Forwarding the activity result to the fragment

* Hellman/dev todo mvvm rxjava (#16)

* Reactive Repository Refactoring

* Fixes from PR review

* Cosmetic fixes and method renaming.

* [WIP] making save task completable

* Making the save return a completable

* Method removed

* Small cleanup around saving the tasks.

* Making the complete and activate methods return Completable

* Making the refresh return a Completable.

* Some tests added.
refresh returns an observable

* Some tests added.

* More tests added

* Adding a UI Model for the Statistics and updating tests

* Renaming the Model to UiModel

* ViewModels improved and tests partially fixed

* Updated the tests for TasksViewModel

* Small formatting improvements

* Removing .share from TaskRepository.getTasks().
Comments updated.

* buildToolsVersion defined in a wrong place

* Added null checks for the weak reference

* Nitpicks fixed: extra <p> removed, new line removed, method renamed

* Updated travis to use correct build tools

* android version updated in travis ci config file

* Updated travis to use android-24 and android-25

* Updating the Injection for prod version

* Separate the creation of the view model and navigators based on the screens. (#18)

* Comments added.
Unused Subject removed. Tests updated

* UI model added for AddEdit tasks

* Comments added for subscriptions

* Upgraded the dependencies, cleaned up the code

* Updated the CI files

* gradle file updated

* NavigatorProvider renamed to Navigator

* Updated the prod version
上级 f5ddba69
......@@ -3,9 +3,10 @@ android:
components:
- tools
- platform-tools
- build-tools-24.0.2
- android-24
- build-tools-26.0.1
- android-26
- extra-android-m2repository
- extra-google-m2repository
jdk:
- oraclejdk8
script:
......@@ -17,4 +18,4 @@ cache:
directories:
- $HOME/.m2
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
- $HOME/.gradle/wrapper/
\ No newline at end of file
# TODO-MVVM-RXJAVA
Project owners: [Erik Hellman](https://github.com/erikhellman) & [Florina Muntenescu (upday)](https://github.com/florina-muntenescu)
Project owner: [Florina Muntenescu](https://github.com/florina-muntenescu)
### Summary
This sample is based on the TODO-MVP project and uses RxJava for communication between the data model and presenter layers.
Compared to the TODO-MVP, both the Presenter contracts and the implementation of the Views stay the same. The changes are done to the data model layer and in the implementation of the Presenters. For the sake of simplicity we decided to keep the RxJava usage minimal, leaving optimizations like RxJava caching aside.
The data model layer exposes RxJava ``Observable`` streams as a way of retrieving tasks. The ``TasksDataSource`` interface contains methods like:
```java
Observable<List<Task>> getTasks();
Observable<Task> getTask(@NonNull String taskId);
```
This is implemented in ``TasksLocalDataSource`` with the help of [SqlBrite](https://github.com/square/sqlbrite). The result of queries to the database being easily exposed as streams of data.
```java
@Override
public Observable<List<Task>> getTasks() {
...
return mDatabaseHelper.createQuery(TaskEntry.TABLE_NAME, sql)
.mapToList(mTaskMapperFunction);
}
```
The ``TasksRepository`` combines the streams of data from the local and the remote data sources, exposing it to whoever needs it. In our project, the Presenters and the unit tests are actually the consumers of these ``Observable``s.
The Presenters subscribe to the ``Observable``s from the ``TasksRepository`` and after manipulating the data, they are the ones that decide what the views should display, in the ``.subscribe(...)`` method. Also, the Presenters are the ones that decide on the working threads. For example, in the ``StatisticsPresenter``, we decide on which thread we should do the computation of the active and completed tasks and what should happen when this computation is done: show the statistics, if all is ok; show loading statistics error, if needed; and telling the view that the loading indicator should not be visible anymore.
```java
...
Subscription subscription = Observable
.zip(completedTasks, activeTasks, new Func2<Integer, Integer, Pair<Integer, Integer>>() {
@Override
public Pair<Integer, Integer> call(Integer completed, Integer active) {
return Pair.create(active, completed);
}
})
.subscribeOn(mSchedulerProvider.computation())
.observeOn(mSchedulerProvider.ui())
.subscribe(new Action1<Pair<Integer, Integer>>() {
@Override
public void call(Pair<Integer, Integer> stats) {
mStatisticsView.showStatistics(stats.first, stats.second);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
mStatisticsView.showLoadingStatisticsError();
}
}, new Action0() {
@Override
public void call() {
mStatisticsView.setProgressIndicator(false);
}
});
```
Handling of the working threads is done with the help of RxJava's `Scheduler`s. For example, the creation of the database together with all the database queries is happening on the IO thread. The `subscribeOn` and `observeOn` methods are used in the Presenter classes to define that the `Observer`s will operate on the computation thread and that the observing is on the main thread.
// TODO
### Dependencies
......@@ -68,10 +12,6 @@ Handling of the working threads is done with the help of RxJava's `Scheduler`s.
* [RxAndroid](https://github.com/ReactiveX/RxAndroid)
* [SqlBrite](https://github.com/square/sqlbrite)
### Java 8 Compatibility
This project uses [lambda expressions](https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html) extensively, one of the features of [Java 8](https://developer.android.com/guide/platform/j8-jack.html). To check out how the translation to lambdas was made, check out [this commit](https://github.com/googlesamples/android-architecture/pull/240/commits/929f63e3657be8705679c46c75e2625dc44a5b28), where lambdas and the Jack compiler were enabled.
## Features
### Complexity - understandability
......@@ -92,11 +32,11 @@ Very High. Given that the RxJava ``Observable``s are highly unit testable, unit
#### UI testing
Similar with TODO-MVP
Similar with TODO-MVP-RxJava
### Code metrics
Compared to TODO-MVP, new classes were added for handing the ``Schedulers`` that provide the working threads.
Compared to TODO-MVP-RxJava,
```
-------------------------------------------------------------------------------
......
......@@ -7,7 +7,7 @@ machine:
dependencies:
pre:
- sudo pip install -U crcmod
- echo y | android update sdk --no-ui --all --filter "tools,platform-tools,build-tools-24.0.2,android-24,extra-android-m2repository"
- echo y | android update sdk --no-ui --all --filter "tools,platform-tools,build-tools-26.0.1,android-26,extra-android-m2repository,extra-google-m2repository"
post:
- cd todoapp;./gradlew :app:assembleDebug -PdisablePreDex
......
......@@ -12,10 +12,6 @@ android {
versionName "1.0"
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
jackOptions {
enabled true
}
}
compileOptions {
......@@ -41,13 +37,17 @@ android {
}
}
// Using gradle plugin 3 you need to specify flavor dimensions.
flavorDimensions 'buildType'
// If you need to add more flavors, consider using flavor dimensions.
productFlavors {
mock {
dimension 'buildType'
applicationIdSuffix = ".mock"
}
prod {
dimension 'buildType'
}
}
......@@ -55,7 +55,7 @@ android {
android.variantFilter { variant ->
if(variant.buildType.name.equals('release')
&& variant.getFlavors().get(0).name.equals('mock')) {
variant.setIgnore(true);
variant.setIgnore(true)
}
}
......
......@@ -63,7 +63,7 @@ public class TestUtils {
*/
public static String getToolbarNavigationContentDescription(
@NonNull Activity activity, @IdRes int toolbar1) {
Toolbar toolbar = (Toolbar) activity.findViewById(toolbar1);
Toolbar toolbar = activity.findViewById(toolbar1);
if (toolbar != null) {
return (String) toolbar.getNavigationContentDescription();
} else {
......
......@@ -38,8 +38,10 @@ import rx.observers.TestSubscriber;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsCollectionContaining.hasItems;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Integration test for the {@link TasksDataSource}, which uses the {@link TasksDbHelper}.
......@@ -58,6 +60,8 @@ public class TasksLocalDataSourceTest {
private TasksLocalDataSource mLocalDataSource;
private Task mTask = new Task(TITLE, "");
@Before
public void setup() {
TasksLocalDataSource.destroyInstance();
......@@ -79,30 +83,26 @@ public class TasksLocalDataSourceTest {
@Test
public void saveTask_retrievesTask() {
// Given a new task
final Task newTask = new Task(TITLE, "");
// When saved into the persistent repository
mLocalDataSource.saveTask(newTask);
mLocalDataSource.saveTask(mTask).subscribe();
// Then the task can be retrieved from the persistent repository
TestSubscriber<Task> testSubscriber = new TestSubscriber<>();
mLocalDataSource.getTask(newTask.getId()).subscribe(testSubscriber);
testSubscriber.assertValue(newTask);
mLocalDataSource.getTask(mTask.getId()).subscribe(testSubscriber);
testSubscriber.assertValue(mTask);
}
@Test
public void completeTask_retrievedTaskIsComplete() {
// Given a new task in the persistent repository
final Task newTask = new Task(TITLE, "");
mLocalDataSource.saveTask(newTask);
mLocalDataSource.saveTask(mTask).subscribe();
// When completed in the persistent repository
mLocalDataSource.completeTask(newTask);
mLocalDataSource.completeTask(mTask).subscribe();
// Then the task can be retrieved from the persistent repository and is complete
TestSubscriber<Task> testSubscriber = new TestSubscriber<>();
mLocalDataSource.getTask(newTask.getId()).subscribe(testSubscriber);
mLocalDataSource.getTask(mTask.getId()).subscribe(testSubscriber);
testSubscriber.assertValueCount(1);
Task result = testSubscriber.getOnNextEvents().get(0);
assertThat(result.isCompleted(), is(true));
......@@ -111,16 +111,15 @@ public class TasksLocalDataSourceTest {
@Test
public void activateTask_retrievedTaskIsActive() {
// Given a new completed task in the persistent repository
final Task newTask = new Task(TITLE, "");
mLocalDataSource.saveTask(newTask);
mLocalDataSource.completeTask(newTask);
mLocalDataSource.saveTask(mTask).subscribe();
mLocalDataSource.completeTask(mTask).subscribe();
// When activated in the persistent repository
mLocalDataSource.activateTask(newTask);
mLocalDataSource.activateTask(mTask).subscribe();
// Then the task can be retrieved from the persistent repository and is active
TestSubscriber<Task> testSubscriber = new TestSubscriber<>();
mLocalDataSource.getTask(newTask.getId()).subscribe(testSubscriber);
mLocalDataSource.getTask(mTask.getId()).subscribe(testSubscriber);
testSubscriber.assertValueCount(1);
Task result = testSubscriber.getOnNextEvents().get(0);
assertThat(result.isActive(), is(true));
......@@ -131,13 +130,13 @@ public class TasksLocalDataSourceTest {
public void clearCompletedTask_taskNotRetrievable() {
// Given 2 new completed tasks and 1 active task in the persistent repository
final Task newTask1 = new Task(TITLE, "");
mLocalDataSource.saveTask(newTask1);
mLocalDataSource.completeTask(newTask1);
mLocalDataSource.saveTask(newTask1).subscribe();
mLocalDataSource.completeTask(newTask1).subscribe();
final Task newTask2 = new Task(TITLE2, "");
mLocalDataSource.saveTask(newTask2);
mLocalDataSource.completeTask(newTask2);
mLocalDataSource.saveTask(newTask2).subscribe();
mLocalDataSource.completeTask(newTask2).subscribe();
final Task newTask3 = new Task(TITLE3, "");
mLocalDataSource.saveTask(newTask3);
mLocalDataSource.saveTask(newTask3).subscribe();
// When completed tasks are cleared in the repository
mLocalDataSource.clearCompletedTasks();
......@@ -152,8 +151,7 @@ public class TasksLocalDataSourceTest {
@Test
public void deleteAllTasks_emptyListOfRetrievedTask() {
// Given a new task in the persistent repository and a mocked callback
Task newTask = new Task(TITLE, "");
mLocalDataSource.saveTask(newTask);
mLocalDataSource.saveTask(mTask).subscribe();
// When all tasks are deleted
mLocalDataSource.deleteAllTasks();
......@@ -169,9 +167,9 @@ public class TasksLocalDataSourceTest {
public void getTasks_retrieveSavedTasks() {
// Given 2 new tasks in the persistent repository
final Task newTask1 = new Task(TITLE, "");
mLocalDataSource.saveTask(newTask1);
mLocalDataSource.saveTask(newTask1).subscribe();
final Task newTask2 = new Task(TITLE, "");
mLocalDataSource.saveTask(newTask2);
mLocalDataSource.saveTask(newTask2).subscribe();
// Then the tasks can be retrieved from the persistent repository
TestSubscriber<List<Task>> testSubscriber = new TestSubscriber<>();
......@@ -188,4 +186,70 @@ public class TasksLocalDataSourceTest {
mLocalDataSource.getTask("1").subscribe(testSubscriber);
testSubscriber.assertValue(null);
}
@Test
public void getTask_emits_whenNewTaskSaved() {
// Given that we are subscribed to the list of tasks
TestSubscriber<List<Task>> testSubscriber = new TestSubscriber<>();
mLocalDataSource.getTasks().subscribe(testSubscriber);
// When adding a new task
mLocalDataSource.saveTask(mTask).subscribe();
// 2 emissions are registered
testSubscriber.assertValueCount(2);
// The first one is an empty list, because the local data source was empty
assertThat(testSubscriber.getOnNextEvents().get(0).isEmpty(), is(true));
// The 2nd one is a list containing the added task
List<Task> tasks = testSubscriber.getOnNextEvents().get(1);
assertThat(tasks.size(), is(1));
assertThat(tasks.get(0), is(mTask));
}
@Test
public void getTask_emits_whenTaskCompleted() {
//Given that a task is saved
mLocalDataSource.saveTask(mTask).subscribe();
// Given that we are subscribed to the list of tasks
TestSubscriber<List<Task>> testSubscriber = new TestSubscriber<>();
mLocalDataSource.getTasks().subscribe(testSubscriber);
// When adding a new task
mLocalDataSource.completeTask(mTask).subscribe();
// 2 emissions are registered
testSubscriber.assertValueCount(2);
// The first one is an empty list, because the local data source was empty
assertThat(testSubscriber.getOnNextEvents().get(0).get(0), is(mTask));
// The 2nd one is a list containing the added task
List<Task> tasks = testSubscriber.getOnNextEvents().get(1);
assertThat(tasks.size(), is(1));
assertTrue(tasks.get(0).isCompleted());
}
@Test
public void saveTask_replacesTask() {
//Given that a task is saved
mLocalDataSource.saveTask(mTask).subscribe();
// Given a task with the same id
Task edited = new Task("edited", "edited", mTask.getId());
// When the task is saved
TestSubscriber testSubscriber = new TestSubscriber();
mLocalDataSource.saveTask(edited).subscribe(testSubscriber);
// No error is emitted
testSubscriber.assertNoErrors();
assertTaskInLocalRepository(edited);
}
private void assertTaskInLocalRepository(Task task) {
// Given that we are subscribed to the list of tasks
TestSubscriber<List<Task>> testSubscriber = new TestSubscriber<>();
mLocalDataSource.getTasks().subscribe(testSubscriber);
List<Task> tasks = testSubscriber.getOnNextEvents().get(0);
assertThat(tasks.size(), is(1));
assertEquals(tasks.get(0), task);
}
}
......@@ -16,13 +16,6 @@
package com.example.android.architecture.blueprints.todoapp.statistics;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.containsString;
import android.content.Intent;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
......@@ -40,6 +33,12 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.containsString;
/**
* Tests for the statistics screen.
*/
......@@ -49,7 +48,6 @@ public class StatisticsScreenTest {
/**
* {@link ActivityTestRule} is a JUnit {@link Rule @Rule} to launch your activity under test.
*
* <p>
* Rules are interceptors which are executed for each test method and are important building
* blocks of Junit tests.
......@@ -61,7 +59,6 @@ public class StatisticsScreenTest {
/**
* Setup your test fixture with a fake task id. The {@link TaskDetailActivity} is started with
* a particular task id, which is then loaded from the service API.
*
* <p>
* Note that this test runs hermetically and is fully isolated using a fake implementation of
* the service API. This is a great way to make your tests more reliable and faster at the same
......@@ -72,8 +69,9 @@ public class StatisticsScreenTest {
// Given some tasks
TasksRepository.destroyInstance();
TasksRepository repository = Injection.provideTasksRepository(InstrumentationRegistry.getContext());
repository.saveTask(new Task("Title1", "", false));
repository.saveTask(new Task("Title2", "", true));
repository.saveTask(new Task("Title1", "", false))
.andThen(repository.saveTask(new Task("Title2", "", true)))
.subscribe();
// Lazily start the Activity from the ActivityTestRule
Intent startIntent = new Intent();
......@@ -83,11 +81,8 @@ public class StatisticsScreenTest {
@Test
public void Tasks_ShowsNonEmptyMessage() throws Exception {
// Check that the active and completed tasks text is displayed
String expectedActiveTaskText = InstrumentationRegistry.getTargetContext()
.getString(R.string.statistics_active_tasks);
onView(withText(containsString(expectedActiveTaskText))).check(matches(isDisplayed()));
String expectedCompletedTaskText = InstrumentationRegistry.getTargetContext()
.getString(R.string.statistics_completed_tasks);
onView(withText(containsString(expectedCompletedTaskText))).check(matches(isDisplayed()));
String expectedText = InstrumentationRegistry.getTargetContext()
.getString(R.string.statistics_active_completed_tasks, 1, 1);
onView(withText(containsString(expectedText))).check(matches(isDisplayed()));
}
}
......@@ -16,15 +16,6 @@
package com.example.android.architecture.blueprints.todoapp.taskdetail;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isChecked;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.IsNot.not;
import android.app.Activity;
import android.content.Intent;
import android.support.test.InstrumentationRegistry;
......@@ -43,6 +34,14 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isChecked;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.IsNot.not;
/**
* Tests for the tasks screen, the main screen which contains a list of all tasks.
*/
......@@ -66,11 +65,9 @@ public class TaskDetailScreenTest {
/**
* {@link ActivityTestRule} is a JUnit {@link Rule @Rule} to launch your activity under test.
*
* <p>
* Rules are interceptors which are executed for each test method and are important building
* blocks of Junit tests.
*
* <p>
* Sometimes an {@link Activity} requires a custom start {@link Intent} to receive data
* from the source Activity. ActivityTestRule has a feature which let's you lazily start the
......@@ -92,7 +89,6 @@ public class TaskDetailScreenTest {
/**
* Setup your test fixture with a fake task id. The {@link TaskDetailActivity} is started with
* a particular task id, which is then loaded from the service API.
*
* <p>
* Note that this test runs hermetically and is fully isolated using a fake implementation of
* the service API. This is a great way to make your tests more reliable and faster at the same
......@@ -102,7 +98,9 @@ public class TaskDetailScreenTest {
// Add a task stub to the fake service api layer.
TasksRepository.destroyInstance();
TasksLocalDataSource.destroyInstance();
Injection.provideTasksRepository(InstrumentationRegistry.getTargetContext()).saveTask(task);
Injection.provideTasksRepository(InstrumentationRegistry.getTargetContext())
.saveTask(task)
.subscribe();
// Lazily start the Activity from the ActivityTestRule this time to inject the start Intent
Intent startIntent = new Intent();
......
......@@ -20,6 +20,7 @@
xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name="com.example.android.architecture.blueprints.todoapp.ToDoApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
......
/*
* Copyright 2016, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.architecture.blueprints.todoapp;
public interface BasePresenter {
void subscribe();
void unsubscribe();
}
/*
* Copyright 2016, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.architecture.blueprints.todoapp;
public interface BaseView<T> {
void setPresenter(T presenter);
}
package com.example.android.architecture.blueprints.todoapp;
import android.app.Application;
import android.os.StrictMode;
/**
* Application class, used for setting the StrictMode.
*/
public class ToDoApplication extends Application {
@Override