Android Data Providing Structure: In Summary
The relationships between SQLite, ContentProvider, ContentResolver
Recently I gave a lecture on using SQLite DB and Content Providers. ( full slides can be found here)
It discussed about many components, and sometimes, we know, things might be confusing. For me, many times understanding the structure, architecture and relationships between components is a key for better understanding.
To shed some more light, let’s summarize the structure.
I like to think about my app architecture as such:
This is a whole subject of its own, it’s based on Clean Architecture principles, but in short:
Data layer — is our actual data structures. It is as objective as possible, in the sense that it has no idea what will this data be used for, how (and even if ) will it be presented, or who will use it.
Presentation / UI layer — is our views, how things end up on the screen. Our RecyclerView, ViewPagers, TextViews etc…. are all here. It knows how to take some data in form of some view models, and present them on the screen. It doesn’t care where was this data originated from or how was it represented back by the server or the database or even maybe statically by the app. It merely takes objects that it knows how to present — and present them.
Domain layer — the data models which are on our data layer, differ from the way the presentation layer needs them. And so, we need someone to be an intermediate, and prepare the data to be presented for a certain use case, a certain scenario.
Let’s go back to our Content Providing session:
We learnt about using SQLite database which is the standard database in Android.
Then we talked about SQLiteOpenHelper, which helps with handling of database’s creation and upgrade.
Both live on our Data layer. They are how we chose to persist the data.
The data is persisted so that someone will use it somehow.
If, for example, I’m saving contacts on my data layer, someone will ask “give me contacts”. Let’s call that one who asks GetContactsUseCase, (who lives on the Domain layer).
We have to remember that as Use Case belongs to a certain app, which requests the data, the data itself can belong to a whole different app.
Should every app which consumes the data be aware of the other app’s database implementation? if Android contacts database changes, should they send all the Android developers emails to let them know about the changes? Should they post on Facebook? or tweet? Off course not!
To be able to share data between apps, we have a fellow named Content Provider, which encapsulates the database implementation, and makes it possible to share data between processes.
Even if this database is only for our own app to use, it’s a good idea to encapsulate the database implementation, so that if we change the database implementation, Content Provider is the only one who knows about it, and not every UseCase who ever requested the data.
Now, Content Provider belongs to the data owner, it’s an abstraction for the database. In order to have a proper abstraction between the content provider and the content requester, we have another friend called Content Resolver.
Each app holds a single Content Resolver, which has a mapping between the content providers and their authorities.
The authority is an Android-internal unique name per content provider. Therefore, when creating our own providers, we use Internet domain ownership (in reverse) as the basis of your provider authority. Same convention we use for our apps package names (such as : com.example.app….)
When an app asks the Content Resolver for an authority, it actually asks for a certain type of content, and will get the right content provider which is in charge of this type of content.
For me, I like to have in my app a single point of contact for each data type. I call it a Repository class, and it can easily decide where to get the data from: a server, cache, database, etc…
With the contacts example:
GetContactsUseCase wants a list of contacts, and it will ask ContactsRepository class.
If ContactsRepository decides to get the data from a database, it will ask the ContentResolver instance, by the authority which represents the data.
The ContentResolver figures out which Content Provider matches the authority requested.
Content Provider, in turn, will ask the database for the data.
For example, to query the Android ContactsProvider which will query the Android Contacts database, we’ll use:
getContentResolver(). query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
To complete the picture :
5. GetContactsUseCase will get the data model (usually in a Cursor)
6. GetContactsUseCase will turn the data models into view models (let’s call it MyContactobjects) , and send them to some ContactsPresenter object (in the Presentation / UI layer) .
7. Our Presentation layer will have some activity, with some RecyclerView, for instance. The ContactsPresenter object knows the RecyclerView’s and its adapter. The adapter only knows how to take MyContact object and present it on the screen. Luckily, that’s the type we received from GetContactsUseCase.
Where did the data of MyContacts come from?
The Android Contacts database? my app local database? the server? did I just made up a list of random objects? — Presentation layer simply doesn’t care.
Hopefully it made some sense for you, and made things a bit clearer.
Thank you for reading!!