It is based on the TODO-MVP-Loaders sample and uses a Content Provider to retrieve data into the repository.
The advantages of Content Providers, from the Content Provider documentation page, are:
- Manage access to a structured set of data
- Content providers are the standard interface that connects data in one process with code running in another process
The advantages of Loaders, from the Loaders documentation page, are:
- They provide asynchronous loading of data, removing the need for callbacks in the repository.
- They monitor the source of their data and deliver new results when the content changes, in our case, the repository.
- They automatically reconnect to the last loader when being recreated after a configuration change.
The data is fetched by Cursor Loaders and delivered to the Presenters. The Presenter does not need to know how the data is loaded, for that reason the LoaderProvider class is passed as a dependency of the Presenter and is in charge of dealing with the data loading.
In src/data/source/LoaderProvider.java:
return new CursorLoader(
mContext,
TasksPersistenceContract.TaskEntry.buildTasksUri(),
TasksPersistenceContract.TaskEntry.TASKS_COLUMNS, selection, selectionArgs, null
);
The results are received in the UI Thread, handled by the Presenter.
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (data != null) {
if (data.moveToLast()) {
onDataLoaded(data);
} else {
onDataEmpty();
}
} else {
onDataNotAvailable();
}
}
Since CursorLoaders have Observers listening to the underlying data changes in the ContentProvider, we don't need to tell them directly that new data is available. For that reason, the Presenter tells the Repository to go and grab the latest data.
/**
* We will always have fresh data from remote, the Loaders handle the local data
*/
public void loadTasks() {
mTasksView.setLoadingIndicator(true);
mTasksRepository.getTasks(this);
}
Upon start, once the data has been stored locally we simply ask the LoaderProvider to start the Loader
@Override
public void onTasksLoaded() {
if (mLoaderManager.getLoader(TASKS_LOADER) == null) {
mLoaderManager.initLoader(TASKS_LOADER, mCurrentFiltering.getFilterExtras(), this);
} else {
mLoaderManager.restartLoader(TASKS_LOADER, mCurrentFiltering.getFilterExtras(), this);
}
}
The TasksRepository behaves a bit differently compared to the other branches. The main difference is that it's not returning data to the Presenter
, but only storing the tasks in the LocalDataSource. Once the ContentProvider
inserts data, it will notify the change to the Uri
and whoever is observing it will receive an update.
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
final SQLiteDatabase db = mTasksDbHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
int rowsDeleted;
switch (match) {
case TASK:
rowsDeleted = db.delete(
TasksPersistenceContract.TaskEntry.TABLE_NAME, selection, selectionArgs);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
if (selection == null || rowsDeleted != 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return rowsDeleted;
}
That's why there's no need to return the data to the Presenter
, the CursorLoader
will always have the data present in the ContentProvider
Content Providers are the perfect solution to share content with other applications. If your application doesn't need to share content then this is not the best approach for you.
One can make the most out of them when using Widgets or you'd like to share content the same way Calendar or Contacts do.
Another benefit of Content Providers is that they don't need to use a SQLite database underneath, one could use other approach to give data to the application. For
example, FileProvider is a great example of that. FileProvider is a special subclass of ContentProvider
that facilitates secure sharing of files associated with an app by creating a content:// Uri for a file instead of a file:/// Uri.
No external frameworks.
Developers need to be familiar with the Loaders and Content Providers framework, which is not trivial. Following this approach is harder to decouple from dependencies of the Android Framework.
The use of the Loaders framework adds a big dependency with the Android framework so unit testing is harder. Same goes for the Content Provider, forcing us to use AndroidJUnit tests which run in a device / emulator and not in the developer's local JVM.
No difference with MVP.
Compared to MVP, the only new classes are LoaderProvider and TasksProvider. Parts of the code are simpler as Loaders take care of the asynchronous work.
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Java 48 1100 1460 3535 (3450 in MVP)
XML 34 97 337 601
-------------------------------------------------------------------------------
SUM: 82 1197 1797 4136
-------------------------------------------------------------------------------
Similar to MVP Loaders
Medium as the Loaders and Content Providers framework are not trivial.