/app/src/main/java/com/kinstalk/musicplayer2/data/source/tasks/TasksRepository.java

https://gitlab.com/szjy-nj/MusicPlayer2 · Java · 311 lines · 199 code · 47 blank · 65 comment · 29 complexity · 2455629e6385c560c075384c15a32c3f MD5 · raw file

  1. /*
  2. * Copyright 2016, The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.kinstalk.musicplayer2.data.source.tasks;
  17. import android.support.annotation.NonNull;
  18. import android.support.annotation.Nullable;
  19. import com.kinstalk.musicplayer2.data.model.tasks.Task;
  20. import java.util.ArrayList;
  21. import java.util.Iterator;
  22. import java.util.LinkedHashMap;
  23. import java.util.List;
  24. import java.util.Map;
  25. import static com.google.common.base.Preconditions.checkNotNull;
  26. /**
  27. * Concrete implementation to load tasks from the data sources into a cache.
  28. * <p/>
  29. * For simplicity, this implements a dumb synchronisation between locally persisted data and data
  30. * obtained from the server, by using the remote data source only if the local database doesn't
  31. * exist or is empty.
  32. */
  33. public class TasksRepository implements TasksDataSource {
  34. private static TasksRepository INSTANCE = null;
  35. private final TasksDataSource mTasksRemoteDataSource;
  36. private final TasksDataSource mTasksLocalDataSource;
  37. /**
  38. * This variable has package local visibility so it can be accessed from tests.
  39. */
  40. Map<String, Task> mCachedTasks;
  41. /**
  42. * Marks the cache as invalid, to force an update the next time data is requested. This variable
  43. * has package local visibility so it can be accessed from tests.
  44. */
  45. boolean mCacheIsDirty = false;
  46. // Prevent direct instantiation.
  47. private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
  48. @NonNull TasksDataSource tasksLocalDataSource) {
  49. mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
  50. mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
  51. }
  52. /**
  53. * Returns the single instance of this class, creating it if necessary.
  54. *
  55. * @param tasksRemoteDataSource the backend data source
  56. * @param tasksLocalDataSource the device storage data source
  57. * @return the {@link TasksRepository} instance
  58. */
  59. public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,
  60. TasksDataSource tasksLocalDataSource) {
  61. if (INSTANCE == null) {
  62. INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);
  63. }
  64. return INSTANCE;
  65. }
  66. /**
  67. * Used to force {@link #getInstance(TasksDataSource, TasksDataSource)} to create a new instance
  68. * next time it's called.
  69. */
  70. public static void destroyInstance() {
  71. INSTANCE = null;
  72. }
  73. /**
  74. * Gets tasks from cache, local data source (SQLite) or remote data source, whichever is
  75. * available first.
  76. * <p/>
  77. * Note: {@link LoadTasksCallback#onDataNotAvailable()} is fired if all data sources fail to
  78. * get the data.
  79. */
  80. @Override
  81. public void getTasks(@NonNull final LoadTasksCallback callback) {
  82. checkNotNull(callback);
  83. // Respond immediately with cache if available and not dirty
  84. if (mCachedTasks != null && !mCacheIsDirty) {
  85. callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
  86. return;
  87. }
  88. if (mCacheIsDirty) {
  89. // If the cache is dirty we need to fetch new data from the network.
  90. getTasksFromRemoteDataSource(callback);
  91. } else {
  92. // Query the local storage if available. If not, query the network.
  93. mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
  94. @Override
  95. public void onTasksLoaded(List<Task> tasks) {
  96. refreshCache(tasks);
  97. callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
  98. }
  99. @Override
  100. public void onDataNotAvailable() {
  101. getTasksFromRemoteDataSource(callback);
  102. }
  103. });
  104. }
  105. }
  106. @Override
  107. public void saveTask(@NonNull Task task) {
  108. checkNotNull(task);
  109. mTasksRemoteDataSource.saveTask(task);
  110. mTasksLocalDataSource.saveTask(task);
  111. // Do in memory cache update to keep the app UI up to date
  112. if (mCachedTasks == null) {
  113. mCachedTasks = new LinkedHashMap<>();
  114. }
  115. mCachedTasks.put(task.getId(), task);
  116. }
  117. @Override
  118. public void completeTask(@NonNull Task task) {
  119. checkNotNull(task);
  120. mTasksRemoteDataSource.completeTask(task);
  121. mTasksLocalDataSource.completeTask(task);
  122. Task completedTask = new Task(task.getTitle(), task.getDescription(), task.getId(), true);
  123. // Do in memory cache update to keep the app UI up to date
  124. if (mCachedTasks == null) {
  125. mCachedTasks = new LinkedHashMap<>();
  126. }
  127. mCachedTasks.put(task.getId(), completedTask);
  128. }
  129. @Override
  130. public void completeTask(@NonNull String taskId) {
  131. checkNotNull(taskId);
  132. completeTask(getTaskWithId(taskId));
  133. }
  134. @Override
  135. public void activateTask(@NonNull Task task) {
  136. checkNotNull(task);
  137. mTasksRemoteDataSource.activateTask(task);
  138. mTasksLocalDataSource.activateTask(task);
  139. Task activeTask = new Task(task.getTitle(), task.getDescription(), task.getId());
  140. // Do in memory cache update to keep the app UI up to date
  141. if (mCachedTasks == null) {
  142. mCachedTasks = new LinkedHashMap<>();
  143. }
  144. mCachedTasks.put(task.getId(), activeTask);
  145. }
  146. @Override
  147. public void activateTask(@NonNull String taskId) {
  148. checkNotNull(taskId);
  149. activateTask(getTaskWithId(taskId));
  150. }
  151. @Override
  152. public void clearCompletedTasks() {
  153. mTasksRemoteDataSource.clearCompletedTasks();
  154. mTasksLocalDataSource.clearCompletedTasks();
  155. // Do in memory cache update to keep the app UI up to date
  156. if (mCachedTasks == null) {
  157. mCachedTasks = new LinkedHashMap<>();
  158. }
  159. Iterator<Map.Entry<String, Task>> it = mCachedTasks.entrySet().iterator();
  160. while (it.hasNext()) {
  161. Map.Entry<String, Task> entry = it.next();
  162. if (entry.getValue().isCompleted()) {
  163. it.remove();
  164. }
  165. }
  166. }
  167. /**
  168. * Gets tasks from local data source (sqlite) unless the table is new or empty. In that case it
  169. * uses the network data source. This is done to simplify the sample.
  170. * <p/>
  171. * Note: {@link LoadTasksCallback#onDataNotAvailable()} is fired if both data sources fail to
  172. * get the data.
  173. */
  174. @Override
  175. public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
  176. checkNotNull(taskId);
  177. checkNotNull(callback);
  178. Task cachedTask = getTaskWithId(taskId);
  179. // Respond immediately with cache if available
  180. if (cachedTask != null) {
  181. callback.onTaskLoaded(cachedTask);
  182. return;
  183. }
  184. // Load from server/persisted if needed.
  185. // Is the task in the local data source? If not, query the network.
  186. mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {
  187. @Override
  188. public void onTaskLoaded(Task task) {
  189. callback.onTaskLoaded(task);
  190. }
  191. @Override
  192. public void onDataNotAvailable() {
  193. mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {
  194. @Override
  195. public void onTaskLoaded(Task task) {
  196. callback.onTaskLoaded(task);
  197. }
  198. @Override
  199. public void onDataNotAvailable() {
  200. callback.onDataNotAvailable();
  201. }
  202. });
  203. }
  204. });
  205. }
  206. @Override
  207. public void refreshTasks() {
  208. mCacheIsDirty = true;
  209. }
  210. @Override
  211. public void deleteAllTasks() {
  212. mTasksRemoteDataSource.deleteAllTasks();
  213. mTasksLocalDataSource.deleteAllTasks();
  214. if (mCachedTasks == null) {
  215. mCachedTasks = new LinkedHashMap<>();
  216. }
  217. mCachedTasks.clear();
  218. }
  219. @Override
  220. public void deleteTask(@NonNull String taskId) {
  221. mTasksRemoteDataSource.deleteTask(checkNotNull(taskId));
  222. mTasksLocalDataSource.deleteTask(checkNotNull(taskId));
  223. mCachedTasks.remove(taskId);
  224. }
  225. private void getTasksFromRemoteDataSource(@NonNull final LoadTasksCallback callback) {
  226. mTasksRemoteDataSource.getTasks(new LoadTasksCallback() {
  227. @Override
  228. public void onTasksLoaded(List<Task> tasks) {
  229. refreshCache(tasks);
  230. refreshLocalDataSource(tasks);
  231. callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
  232. }
  233. @Override
  234. public void onDataNotAvailable() {
  235. callback.onDataNotAvailable();
  236. }
  237. });
  238. }
  239. private void refreshCache(List<Task> tasks) {
  240. if (mCachedTasks == null) {
  241. mCachedTasks = new LinkedHashMap<>();
  242. }
  243. mCachedTasks.clear();
  244. for (Task task : tasks) {
  245. mCachedTasks.put(task.getId(), task);
  246. }
  247. mCacheIsDirty = false;
  248. }
  249. private void refreshLocalDataSource(List<Task> tasks) {
  250. mTasksLocalDataSource.deleteAllTasks();
  251. for (Task task : tasks) {
  252. mTasksLocalDataSource.saveTask(task);
  253. }
  254. }
  255. @Nullable
  256. private Task getTaskWithId(@NonNull String id) {
  257. checkNotNull(id);
  258. if (mCachedTasks == null || mCachedTasks.isEmpty()) {
  259. return null;
  260. } else {
  261. return mCachedTasks.get(id);
  262. }
  263. }
  264. }