概述
透過todo-mvp來說明MVP中的Model
todo-mvp 是 Android 官方用來說明 MVP Pattern的範例,參考 https://github.com/googlesamples/android-architecture
todo-mvp 裡的 Model 為TaskRepository,TaskRepository繼承TasksDataSource,而TaskDataSource實際上是一個interface,其中2個內部介面LoadTasksCallback和GetTasksCallback用來作callback使用,內部介面的onTasksLoaded方法用來當取得task成功之後把task傳回呼叫點的用途,而onDataNotAvailable方法用來當取得task失敗後的後續處理。
其餘在TasksDataSource介面的方法都是存取資料的共用方法,只要是Model都要實作這些方法。
TasksDataSource.java
public interface TasksDataSource {  
    
      interface LoadTasksCallback {  
    
          void onTasksLoaded(List<Task> tasks);  
    
          void onDataNotAvailable();  
      }  
    
      interface GetTaskCallback {  
    
          void onTaskLoaded(Task task);  
    
          void onDataNotAvailable();  
      }  
    
      void getTasks(@NonNull LoadTasksCallback callback);  
    
      void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);  
    
      void saveTask(@NonNull Task task);  
    
      void completeTask(@NonNull Task task);  
    
      void completeTask(@NonNull String taskId);  
    
      void activateTask(@NonNull Task task);  
    
      void activateTask(@NonNull String taskId);  
    
      void clearCompletedTasks();  
    
      void refreshTasks();  
    
      void deleteAllTasks();  
    
      void deleteTask(@NonNull String taskId);  
  }  
    接著看Presenter如何關聯 Model以及使用Model。把焦點放在AddEditTaskPresenter。
AddEditTaskPresenter本身不會持有任何資料,資料放在Model中。
Presenter會通知Model去改變資料。
Presenter會持有Model和View的變數並在建構式初始化他們。
Presenter會在建構式初始化Model,接著在需要改變資料的位置去操縱Model改變資料,Model改變資料後Presenter再通知View重新載入資料。
AddEditTaskPresenter.java
public class AddEditTaskPresenter implements AddEditTaskContract.Presenter,
TasksDataSource.GetTaskCallback {
@NonNull
private final TasksDataSource mTasksRepository;
@NonNull
private final AddEditTaskContract.View mAddTaskView;
@Nullable
private String mTaskId;
private boolean mIsDataMissing;
/**
* Creates a presenter for the add/edit view.
*
* @param taskId ID of the task to edit or null for a new task
* @param tasksRepository a repository of data for tasks
* @param addTaskView the add/edit view
* @param shouldLoadDataFromRepo whether data needs to be loaded or not (for config changes)
*/
public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
@NonNull AddEditTaskContract.View addTaskView, boolean shouldLoadDataFromRepo) {
mTaskId = taskId;
mTasksRepository = checkNotNull(tasksRepository);
mAddTaskView = checkNotNull(addTaskView);
mIsDataMissing = shouldLoadDataFromRepo;
mAddTaskView.setPresenter(this);
}
@Override
public void start() {
if (!isNewTask() && mIsDataMissing) {
populateTask();
}
}
@Override
public void saveTask(String title, String description) {
if (isNewTask()) {
createTask(title, description);
} else {
updateTask(title, description);
}
}
@Override
public void populateTask() {
if (isNewTask()) {
throw new RuntimeException("populateTask() was called but task is new.");
}
mTasksRepository.getTask(mTaskId, this);
}
@Override
public void onTaskLoaded(Task task) {
// The view may not be able to handle UI updates anymore
if (mAddTaskView.isActive()) {
mAddTaskView.setTitle(task.getTitle());
mAddTaskView.setDescription(task.getDescription());
}
mIsDataMissing = false;
}
@Override
public void onDataNotAvailable() {
// The view may not be able to handle UI updates anymore
if (mAddTaskView.isActive()) {
mAddTaskView.showEmptyTaskError();
}
}
@Override
public boolean isDataMissing() {
return mIsDataMissing;
}
private boolean isNewTask() {
return mTaskId == null;
}
private void createTask(String title, String description) {
Task newTask = new Task(title, description);
if (newTask.isEmpty()) {
mAddTaskView.showEmptyTaskError();
} else {
mTasksRepository.saveTask(newTask);
mAddTaskView.showTasksList();
}
}
private void updateTask(String title, String description) {
if (isNewTask()) {
throw new RuntimeException("updateTask() was called but task is new.");
}
mTasksRepository.saveTask(new Task(title, description, mTaskId));
mAddTaskView.showTasksList(); // After an edit, go back to the list.
}
}
注意雖然Model的變數型態為TasksDataSource(interface),但在Presenter建構式傳入的其實是TaskRepository(繼承自TasksDataSource)。
因此我們需要看的是TasksRepository的內容。
TasksRepository.java
public class TasksRepository implements TasksDataSource {  
    
      private static TasksRepository INSTANCE = null;  
    
      private final TasksDataSource mTasksRemoteDataSource;  
    
      private final TasksDataSource mTasksLocalDataSource;  
    
      /**  
       * This variable has package local visibility so it can be accessed from tests.  
       */  
      Map<String, Task> mCachedTasks;  
    
      /**  
       * Marks the cache as invalid, to force an update the next time data is requested. This variable  
       * has package local visibility so it can be accessed from tests.  
       */  
      boolean mCacheIsDirty = false;  
    
      // Prevent direct instantiation.  
      private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,  
                              @NonNull TasksDataSource tasksLocalDataSource) {  
          mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);  
          mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);  
      }  
    
      /**  
       * Returns the single instance of this class, creating it if necessary.  
       *  
       * @param tasksRemoteDataSource the backend data source  
       * @param tasksLocalDataSource  the device storage data source  
       * @return the {@link TasksRepository} instance  
       */  
      public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,  
                                                TasksDataSource tasksLocalDataSource) {  
          if (INSTANCE == null) {  
              INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);  
          }  
          return INSTANCE;  
      }  
    
      /**  
       * Used to force {@link #getInstance(TasksDataSource, TasksDataSource)} to create a new instance  
       * next time it's called.  
       */  
      public static void destroyInstance() {  
          INSTANCE = null;  
      }  
    
      /**  
       * Gets tasks from cache, local data source (SQLite) or remote data source, whichever is  
       * available first.  
       * <p>  
       * Note: {@link LoadTasksCallback#onDataNotAvailable()} is fired if all data sources fail to  
       * get the data.  
       */  
      @Override  
      public void getTasks(@NonNull final LoadTasksCallback callback) {  
          checkNotNull(callback);  
    
          // Respond immediately with cache if available and not dirty  
          if (mCachedTasks != null && !mCacheIsDirty) {  
              callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));  
              return;  
          }  
    
          if (mCacheIsDirty) {  
              // If the cache is dirty we need to fetch new data from the network.  
              getTasksFromRemoteDataSource(callback);  
          } else {  
              // Query the local storage if available. If not, query the network.  
              mTasksLocalDataSource.getTasks(new LoadTasksCallback() {  
                  @Override  
                  public void onTasksLoaded(List<Task> tasks) {  
                      refreshCache(tasks);  
                      callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));  
                  }  
    
                  @Override  
                  public void onDataNotAvailable() {  
                      getTasksFromRemoteDataSource(callback);  
                  }  
              });  
          }  
      }  
    
      @Override  
      public void saveTask(@NonNull Task task) {  
          checkNotNull(task);  
          mTasksRemoteDataSource.saveTask(task);  
          mTasksLocalDataSource.saveTask(task);  
    
          // Do in memory cache update to keep the app UI up to date  
          if (mCachedTasks == null) {  
              mCachedTasks = new LinkedHashMap<>();  
          }  
          mCachedTasks.put(task.getId(), task);  
      }  
    
      @Override  
      public void completeTask(@NonNull Task task) {  
          checkNotNull(task);  
          mTasksRemoteDataSource.completeTask(task);  
          mTasksLocalDataSource.completeTask(task);  
    
          Task completedTask = new Task(task.getTitle(), task.getDescription(), task.getId(), true);  
    
          // Do in memory cache update to keep the app UI up to date  
          if (mCachedTasks == null) {  
              mCachedTasks = new LinkedHashMap<>();  
          }  
          mCachedTasks.put(task.getId(), completedTask);  
      }  
    
      @Override  
      public void completeTask(@NonNull String taskId) {  
          checkNotNull(taskId);  
          completeTask(getTaskWithId(taskId));  
      }  
    
      @Override  
      public void activateTask(@NonNull Task task) {  
          checkNotNull(task);  
          mTasksRemoteDataSource.activateTask(task);  
          mTasksLocalDataSource.activateTask(task);  
    
          Task activeTask = new Task(task.getTitle(), task.getDescription(), task.getId());  
    
          // Do in memory cache update to keep the app UI up to date  
          if (mCachedTasks == null) {  
              mCachedTasks = new LinkedHashMap<>();  
          }  
          mCachedTasks.put(task.getId(), activeTask);  
      }  
    
      @Override  
      public void activateTask(@NonNull String taskId) {  
          checkNotNull(taskId);  
          activateTask(getTaskWithId(taskId));  
      }  
    
      @Override  
      public void clearCompletedTasks() {  
          mTasksRemoteDataSource.clearCompletedTasks();  
          mTasksLocalDataSource.clearCompletedTasks();  
    
          // Do in memory cache update to keep the app UI up to date  
          if (mCachedTasks == null) {  
              mCachedTasks = new LinkedHashMap<>();  
          }  
          Iterator<Map.Entry<String, Task>> it = mCachedTasks.entrySet().iterator();  
          while (it.hasNext()) {  
              Map.Entry<String, Task> entry = it.next();  
              if (entry.getValue().isCompleted()) {  
                  it.remove();  
              }  
          }  
      }  
    
      /**  
       * Gets tasks from local data source (sqlite) unless the table is new or empty. In that case it  
       * uses the network data source. This is done to simplify the sample.  
       * <p>  
       * Note: {@link GetTaskCallback#onDataNotAvailable()} is fired if both data sources fail to  
       * get the data.  
       */  
      @Override  
      public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {  
          checkNotNull(taskId);  
          checkNotNull(callback);  
    
          Task cachedTask = getTaskWithId(taskId);  
    
          // Respond immediately with cache if available  
          if (cachedTask != null) {  
              callback.onTaskLoaded(cachedTask);  
              return;  
          }  
    
          // Load from server/persisted if needed.  
    
          // Is the task in the local data source? If not, query the network.  
          mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {  
              @Override  
              public void onTaskLoaded(Task task) {  
                  // Do in memory cache update to keep the app UI up to date  
                  if (mCachedTasks == null) {  
                      mCachedTasks = new LinkedHashMap<>();  
                  }  
                  mCachedTasks.put(task.getId(), task);  
                  callback.onTaskLoaded(task);  
              }  
    
              @Override  
              public void onDataNotAvailable() {  
                  mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {  
                      @Override  
                      public void onTaskLoaded(Task task) {  
                          // Do in memory cache update to keep the app UI up to date  
                          if (mCachedTasks == null) {  
                              mCachedTasks = new LinkedHashMap<>();  
                          }  
                          mCachedTasks.put(task.getId(), task);  
                          callback.onTaskLoaded(task);  
                      }  
    
                      @Override  
                      public void onDataNotAvailable() {  
                          callback.onDataNotAvailable();  
                      }  
                  });  
              }  
          });  
      }  
    
      @Override  
      public void refreshTasks() {  
          mCacheIsDirty = true;  
      }  
    
      @Override  
      public void deleteAllTasks() {  
          mTasksRemoteDataSource.deleteAllTasks();  
          mTasksLocalDataSource.deleteAllTasks();  
    
          if (mCachedTasks == null) {  
              mCachedTasks = new LinkedHashMap<>();  
          }  
          mCachedTasks.clear();  
      }  
    
      @Override  
      public void deleteTask(@NonNull String taskId) {  
          mTasksRemoteDataSource.deleteTask(checkNotNull(taskId));  
          mTasksLocalDataSource.deleteTask(checkNotNull(taskId));  
    
          mCachedTasks.remove(taskId);  
      }  
    
      private void getTasksFromRemoteDataSource(@NonNull final LoadTasksCallback callback) {  
          mTasksRemoteDataSource.getTasks(new LoadTasksCallback() {  
              @Override  
              public void onTasksLoaded(List<Task> tasks) {  
                  refreshCache(tasks);  
                  refreshLocalDataSource(tasks);  
                  callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));  
              }  
    
              @Override  
              public void onDataNotAvailable() {  
                  callback.onDataNotAvailable();  
              }  
          });  
      }  
    
      private void refreshCache(List<Task> tasks) {  
          if (mCachedTasks == null) {  
              mCachedTasks = new LinkedHashMap<>();  
          }  
          mCachedTasks.clear();  
          for (Task task : tasks) {  
              mCachedTasks.put(task.getId(), task);  
          }  
          mCacheIsDirty = false;  
      }  
    
      private void refreshLocalDataSource(List<Task> tasks) {  
          mTasksLocalDataSource.deleteAllTasks();  
          for (Task task : tasks) {  
              mTasksLocalDataSource.saveTask(task);  
          }  
      }  
    
      @Nullable  
      private Task getTaskWithId(@NonNull String id) {  
          checkNotNull(id);  
          if (mCachedTasks == null || mCachedTasks.isEmpty()) {  
              return null;  
          } else {  
              return mCachedTasks.get(id);  
          }  
      }  
  }  
    TasksRepository實現3層緩存,首先第1層緩存為記憶體也就是 Map<String, Task> mCachedTasks;
第2層緩存為本地端資料來源,private final TasksDataSource mTasksLocalDataSource;
因為該變數的型態也是TasksDataSource為interface,因此不會被資料來源的實現綁住,也就是說若想更換不同的資料庫,也只要新增TasksDataSource的子類別繼承TasksDataSource即可。
第3層緩存為遠端資料來源,private final TasksDataSource mTasksRemoteDataSource;
變數型態也是TasksDataSource,也可以簡單替換遠端來源的實現,如volley, okhttp, retrofit等等。
若以儲存資料來說,在順序性來說沒有分別,這3層都會儲存資料,如下面的TasksRepository.saveTask方法的實作內容
@Override
public void saveTask(@NonNull Task task) {
checkNotNull(task);
mTasksRemoteDataSource.saveTask(task);
mTasksLocalDataSource.saveTask(task);
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), task);
}
若是讀取資料,則會先從第1層緩存記憶體(mCachedTasks)去讀取資料,若資料存在就直接回傳,若資料不存在,則從第2層緩存本地端資料庫(mTasksLocalDataSource)去讀取資料,若有資料則把該資料加到記憶體(mCachedTasks)後再回傳資料。
若還是沒有資料則從第3層緩存遠端網路(mTasksRemoteDataSource)去讀取資料,若有資料則把該資料加到記憶體(mCachedTasks)後再回傳資料,若資料不存在則顯示該資料不存在訊息。
如下方的TasksRepository.getTask方法內容
@Override
public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
checkNotNull(taskId);
checkNotNull(callback);
Task cachedTask = getTaskWithId(taskId);
// Respond immediately with cache if available
if (cachedTask != null) {
callback.onTaskLoaded(cachedTask);
return;
}
// Load from server/persisted if needed.
// Is the task in the local data source? If not, query the network.
mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), task);
callback.onTaskLoaded(task);
}
@Override
public void onDataNotAvailable() {
mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), task);
callback.onTaskLoaded(task);
}
@Override
public void onDataNotAvailable() {
callback.onDataNotAvailable();
}
});
}
});
}
接著來看看在TasksRepository建構式,存取權限為私有,代表只能透過該纇別內部呼叫。
// Prevent direct instantiation.
private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
@NonNull TasksDataSource tasksLocalDataSource) {
mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
}
呼叫該建構式的位置為getInstance()方法,會透過其方法的參數設定tasksRemoteDataSource和tasksLocalDataSource。
public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,
TasksDataSource tasksLocalDataSource) {
if (INSTANCE == null) {
INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);
}
return INSTANCE;
}
而getInstance方法的呼叫者為Injection類別的ProvideTasksRepository方法。
public class Injection {  
    
      public static TasksRepository provideTasksRepository(@NonNull Context context) {  
          checkNotNull(context);  
          ToDoDatabase database = ToDoDatabase.getInstance(context);  
          return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),  
                  TasksLocalDataSource.getInstance(new AppExecutors(),  
                          database.taskDao()));  
      }  
  }  
    可以看到provideTasksRepository方法內即為FakeTasksRemoteDataSource.getIntance()和TasksLocalDataSource.getInstance()分別代表遠端資料來源和本地端資料來源。
接著看看FakeTasksRemoteDataSource類別。
其內容相當簡單,儲存資料的方式是透過一個Map<String, Task> TASKS_SERVICE_DATA 來儲存資料。
FakeTasksRemoteDataSource.java
public class FakeTasksRemoteDataSource implements TasksDataSource {  
    
      private static FakeTasksRemoteDataSource INSTANCE;  
    
      private static final Map<String, Task> TASKS_SERVICE_DATA = new LinkedHashMap<>();  
    
      // Prevent direct instantiation.  
      private FakeTasksRemoteDataSource() {}  
    
      public static FakeTasksRemoteDataSource getInstance() {  
          if (INSTANCE == null) {  
              INSTANCE = new FakeTasksRemoteDataSource();  
          }  
          return INSTANCE;  
      }  
    
      @Override  
      public void getTasks(@NonNull LoadTasksCallback callback) {  
          callback.onTasksLoaded(Lists.newArrayList(TASKS_SERVICE_DATA.values()));  
      }  
    
      @Override  
      public void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback) {  
          Task task = TASKS_SERVICE_DATA.get(taskId);  
          callback.onTaskLoaded(task);  
      }  
    
      @Override  
      public void saveTask(@NonNull Task task) {  
          TASKS_SERVICE_DATA.put(task.getId(), task);  
      }  
    
      @Override  
      public void completeTask(@NonNull Task task) {  
          Task completedTask = new Task(task.getTitle(), task.getDescription(), task.getId(), true);  
          TASKS_SERVICE_DATA.put(task.getId(), completedTask);  
      }  
    
      @Override  
      public void completeTask(@NonNull String taskId) {  
          // Not required for the remote data source.  
      }  
    
      @Override  
      public void activateTask(@NonNull Task task) {  
          Task activeTask = new Task(task.getTitle(), task.getDescription(), task.getId());  
          TASKS_SERVICE_DATA.put(task.getId(), activeTask);  
      }  
    
      @Override  
      public void activateTask(@NonNull String taskId) {  
          // Not required for the remote data source.  
      }  
    
      @Override  
      public void clearCompletedTasks() {  
          Iterator<Map.Entry<String, Task>> it = TASKS_SERVICE_DATA.entrySet().iterator();  
          while (it.hasNext()) {  
              Map.Entry<String, Task> entry = it.next();  
              if (entry.getValue().isCompleted()) {  
                  it.remove();  
              }  
          }  
      }  
    
      public void refreshTasks() {  
          // Not required because the {@link TasksRepository} handles the logic of refreshing the  
          // tasks from all the available data sources.  
      }  
    
      @Override  
      public void deleteTask(@NonNull String taskId) {  
          TASKS_SERVICE_DATA.remove(taskId);  
      }  
    
      @Override  
      public void deleteAllTasks() {  
          TASKS_SERVICE_DATA.clear();  
      }  
    
      @VisibleForTesting  
      public void addTasks(Task... tasks) {  
          for (Task task : tasks) {  
              TASKS_SERVICE_DATA.put(task.getId(), task);  
          }  
      }  
  }  
    最後看看 TasksLocalDataSource 類別。
該類別有TaskDao以及AppExecutors 變數,其中TaskDao提供存取Task的介面,為應用Room的寫法,關於Room可以參考這篇。而AppExecutors主要負責Executor的執行。
TasksLocalDataSource.java
public class TasksLocalDataSource implements TasksDataSource {  
    
      private static volatile TasksLocalDataSource INSTANCE;  
    
      private TasksDao mTasksDao;  
    
      private AppExecutors mAppExecutors;  
    
      // Prevent direct instantiation.  
      private TasksLocalDataSource(@NonNull AppExecutors appExecutors,  
              @NonNull TasksDao tasksDao) {  
          mAppExecutors = appExecutors;  
          mTasksDao = tasksDao;  
      }  
    
      public static TasksLocalDataSource getInstance(@NonNull AppExecutors appExecutors,  
              @NonNull TasksDao tasksDao) {  
          if (INSTANCE == null) {  
              synchronized (TasksLocalDataSource.class) {  
                  if (INSTANCE == null) {  
                      INSTANCE = new TasksLocalDataSource(appExecutors, tasksDao);  
                  }  
              }  
          }  
          return INSTANCE;  
      }  
    
      /**  
       * Note: {@link LoadTasksCallback#onDataNotAvailable()} is fired if the database doesn't exist  
       * or the table is empty.  
       */  
      @Override  
      public void getTasks(@NonNull final LoadTasksCallback callback) {  
          Runnable runnable = new Runnable() {  
              @Override  
              public void run() {  
                  final List<Task> tasks = mTasksDao.getTasks();  
                  mAppExecutors.mainThread().execute(new Runnable() {  
                      @Override  
                      public void run() {  
                          if (tasks.isEmpty()) {  
                              // This will be called if the table is new or just empty.  
                              callback.onDataNotAvailable();  
                          } else {  
                              callback.onTasksLoaded(tasks);  
                          }  
                      }  
                  });  
              }  
          };  
    
          mAppExecutors.diskIO().execute(runnable);  
      }  
    
      /**  
       * Note: {@link GetTaskCallback#onDataNotAvailable()} is fired if the {@link Task} isn't  
       * found.  
       */  
      @Override  
      public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {  
          Runnable runnable = new Runnable() {  
              @Override  
              public void run() {  
                  final Task task = mTasksDao.getTaskById(taskId);  
    
                  mAppExecutors.mainThread().execute(new Runnable() {  
                      @Override  
                      public void run() {  
                          if (task != null) {  
                              callback.onTaskLoaded(task);  
                          } else {  
                              callback.onDataNotAvailable();  
                          }  
                      }  
                  });  
              }  
          };  
    
          mAppExecutors.diskIO().execute(runnable);  
      }  
    
      @Override  
      public void saveTask(@NonNull final Task task) {  
          checkNotNull(task);  
          Runnable saveRunnable = new Runnable() {  
              @Override  
              public void run() {  
                  mTasksDao.insertTask(task);  
              }  
          };  
          mAppExecutors.diskIO().execute(saveRunnable);  
      }  
    
      @Override  
      public void completeTask(@NonNull final Task task) {  
          Runnable completeRunnable = new Runnable() {  
              @Override  
              public void run() {  
                  mTasksDao.updateCompleted(task.getId(), true);  
              }  
          };  
    
          mAppExecutors.diskIO().execute(completeRunnable);  
      }  
    
      @Override  
      public void completeTask(@NonNull String taskId) {  
          // Not required for the local data source because the {@link TasksRepository} handles  
          // converting from a {@code taskId} to a {@link task} using its cached data.  
      }  
    
      @Override  
      public void activateTask(@NonNull final Task task) {  
          Runnable activateRunnable = new Runnable() {  
              @Override  
              public void run() {  
                  mTasksDao.updateCompleted(task.getId(), false);  
              }  
          };  
          mAppExecutors.diskIO().execute(activateRunnable);  
      }  
    
      @Override  
      public void activateTask(@NonNull String taskId) {  
          // Not required for the local data source because the {@link TasksRepository} handles  
          // converting from a {@code taskId} to a {@link task} using its cached data.  
      }  
    
      @Override  
      public void clearCompletedTasks() {  
          Runnable clearTasksRunnable = new Runnable() {  
              @Override  
              public void run() {  
                  mTasksDao.deleteCompletedTasks();  
    
              }  
          };  
    
          mAppExecutors.diskIO().execute(clearTasksRunnable);  
      }  
    
      @Override  
      public void refreshTasks() {  
          // Not required because the {@link TasksRepository} handles the logic of refreshing the  
          // tasks from all the available data sources.  
      }  
    
      @Override  
      public void deleteAllTasks() {  
          Runnable deleteRunnable = new Runnable() {  
              @Override  
              public void run() {  
                  mTasksDao.deleteTasks();  
              }  
          };  
    
          mAppExecutors.diskIO().execute(deleteRunnable);  
      }  
    
      @Override  
      public void deleteTask(@NonNull final String taskId) {  
          Runnable deleteRunnable = new Runnable() {  
              @Override  
              public void run() {  
                  mTasksDao.deleteTaskById(taskId);  
              }  
          };  
    
          mAppExecutors.diskIO().execute(deleteRunnable);  
      }  
    
      @VisibleForTesting  
      static void clearInstance() {  
          INSTANCE = null;  
      }  
  }  
    以上便是 todo-mvp 的 Model 內容。
Orignal From: Model in todo-mvp

