Content Providers are one of the core component of the Android system. Content Providers play a very important role in terms of data sharing between Android apps. Let’s quick dive into another useful component of Android.
1. What are Content Providers in Android?
Content providers are a centralized repository that supplies data from one application to another upon request. They help to securely access and modify central data. Android system allows Content Providers to store data in several ways. Data can be stored in a database, in files, or even over a network. The image below represents the role of content providers in the Android system.
Binder is remote method invocation system, it is Android specific interprocess communication machanism.
Ashmem is asynchronous shared memory subsystem, it is not available for applications, it used by low level system processes/software.
2. How do Content Providers work?
The application’s UI (Activity or fragment) uses the Cursor Loader class to send query request to the Content Resolver. A Cursor Loader is used to run queries asynchronously in the background.
Content Resolver, as its name suggests, takes query request and sends the request to the Content Provider.
Now the Content Provider processes the request (like create, update, read and delete) and provides the desired result.
Basically, Content Resolvers are used to create data request and Content Providers are used for data response.
The diagram represent the process in pictorial format.
In order to share the data, Content Providers have specific permissions to allow or prohibit other applications from interfering with data.
2.1 Example of Accessing data using Content Providers
Let’s take an example, suppose an app (what’s app, skype, etc) wants to access contacts data from the contacts app.
To access the contacts, the app have to send a query through the ContentResolver object. The ContentResolver object sends the request to the ContentProvider. The ContentProvider will check the request and provide a list of contacts in the response to the app. The format of the response data is an object of the Cursor class, so the app have to retrieve the contacts data by iterating over the cursor object.
3. Content URI
Content URI is an important key in Content Providers. Content URI is a URI (uniform resource identifiers) used to identify data in Content Providers. It consists of two things: Authority and Path. The syntax of Content URI is described below.
content://authority/path
where,
→ content:// is scheme portion of content URI, it describes that a URI is Content URI.
→ authority represents name of content providers
→ path used to identify tables
Custom Content URI:
• content://com.example.provider/articles
Readymade Content URI:
• ContactsContract.CommonDataKinds.Phone.CONTENT_URI
• ContactsContract.Contacts.CONTENT_URI
4. Operations of Content Providers (CRUD operations)
Fundamentally, we can perform four operations through Content Providers. These are Create, Read, Update and Delete (CRUD operations).
Operation | Description |
CREAT | To create data in Content Providers |
READ | Retrieve data from a Content Provider |
UPDATE | To modify existing data |
DELETE | Remove existing data from the storage |
5. Methods of Content Providers
The ContentProvider class has some useful methods that will help us perform our tasks.
Operation | Description |
onCreate() | It will get call when the provider is started |
query() | This method receives requests from clients and provides result in the form of Cursor object |
insert() | Used to insert new record in the content provider |
update() | It updates existing record from the content provider |
delete() | It deletes existing record from the content provider |
getType() | Returns MIME type of data |
6. Example
Let’s take an example to implement Content Providers. In this example we will retrieve data from the device.
6.1 Create New Project
Create a new project in Android Studio from File ⇒ New Project and select Empty Activity from the templates.
6.2 Add permissions in AndroidManifest
We need permission to read contacts from device. Add READ_CONTACT permission to AndroidManifest.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.READ_CONTACTS" /> <application> ... ... <activity/> ... ... </application> </manifest>
6.3 Enable View Binding
In this example we are using Android Jetpack’s feature view binding.
plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' } android { ... ... buildFeatures { viewBinding = true } ... ... } dependencies { ... ... ... }
6.4 Get Contacts from Android Device
Open MainActivity and add the following code. Everything is explained in the code.
package com.androidchunk.contentprovidertest import android.Manifest import android.content.pm.PackageManager import android.os.Build import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.provider.ContactsContract import com.androidchunk.contentprovidertest.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { //view binding lateinit var binding: ActivityMainBinding //permission request code private val myReqCode = 250 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) //permission required for Android M and above if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //check if permission granted if (checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { //permission granted, get contacts getContactsFromDevice() } else { //required permission not granted, request permission requestPermissions(arrayOf(Manifest.permission.READ_CONTACTS), myReqCode) } } else { //permission not required (hahoooo......) getContactsFromDevice() } } private fun getContactsFromDevice() { //Table name in the provider val contactUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI //columns to select val projection = arrayOf( ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER ) //criteria for selecting row val selectionClause = null //arguments val selectionArguments = null //sort order val sortOrder = null /* Raw Query ContentResolver Query SELECT COLUMN1, COLUMN2 ---> projection FROM table ---> contactUri WHERE COLUMN1 = value ---> selectionClause and selectionArguments ORDER BY COLUMN2 ---> sortOrder */ //contentResolver.query(...) returns cursor object val cursor = contentResolver.query( contactUri, projection, selectionClause, selectionArguments, sortOrder ) //our contacts string val stringBuilder = StringBuilder() //check cursor and iterate over if (cursor != null && cursor.moveToFirst()) { while (cursor.moveToNext()) { /* * Name: Gail Powell * Phone: (304) 425-4739 * * Name: Jeanne Malone * Phone: (810) 733-7957 * */ stringBuilder.append("Name: ") stringBuilder.append(cursor.getString(0)) stringBuilder.append("\n") stringBuilder.append("Phone: ") stringBuilder.append(cursor.getString(1)) stringBuilder.append("\n") stringBuilder.append("\n") } //close the cursor object cursor.close() } else { stringBuilder.append("No contacts in device") } //set string value in textView binding.contactTextView.text = stringBuilder } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when (requestCode) { myReqCode -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //permission granted, get contacts getContactsFromDevice() } else { //permission denied, explain.... } } else -> Unit } } }
package com.androidchunk.contentprovidertest; import android.Manifest; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.provider.ContactsContract; import com.androidchunk.contentprovidertest.databinding.ActivityMainBinding; public class MainActivity extends AppCompatActivity { //view binding private ActivityMainBinding binding; //permission request code private int myReqCode = 250; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); //permission required for Android M and above if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //check if permission granted if (checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { //permission granted, get contacts getContactsFromDevice(); } else { //required permission not granted, request permission requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, myReqCode); } } else { //permission not required (hahoooo......) getContactsFromDevice(); } } private void getContactsFromDevice() { //Table name in the provider Uri contactUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI; //columns to select String[] projection = new String[]{ ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER}; //criteria for selecting row String selectionClause = null; //arguments String[] selectionArguments = null; //sort order String sortOrder = null; /* Raw Query ContentResolver Query SELECT COLUMN1, COLUMN2 ---> projection FROM table ---> contactUri WHERE COLUMN1 = value ---> selectionClause and selectionArguments ORDER BY COLUMN2 ---> sortOrder */ //contentResolver.query(...) returns cursor object Cursor cursor = getContentResolver().query( contactUri, projection, selectionClause, selectionArguments, sortOrder ); //our contacts string StringBuilder stringBuilder = new StringBuilder(); //check cursor and iterate over if (cursor != null && cursor.moveToFirst()) { while (cursor.moveToNext()) { /* * Name: Gail Powell * Phone: (304) 425-4739 * * Name: Jeanne Malone * Phone: (810) 733-7957 * */ stringBuilder.append("Name: "); stringBuilder.append(cursor.getString(0)); stringBuilder.append("\n"); stringBuilder.append("Phone: "); stringBuilder.append(cursor.getString(1)); stringBuilder.append("\n"); stringBuilder.append("\n"); } //close the cursor object cursor.close(); } else { stringBuilder.append("No contacts in device"); } //set string value in textView binding.contactTextView.setText(stringBuilder); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == myReqCode) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //permission granted, get contacts getContactsFromDevice(); } else { //permission denied, explain.... } } } }
Comparison of ContentResolver query with SQL Query
Activity Layout:
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/contactTextView" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </ScrollView>
package com.androidchunk.contentprovidertest; import android.Manifest; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.provider.ContactsContract; import com.androidchunk.contentprovidertest.databinding.ActivityMainBinding; public class MainActivity extends AppCompatActivity { //view binding private ActivityMainBinding binding; //permission request code private int myReqCode = 250; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); //permission required for Android M and above if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //check if permission granted if (checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { //permission granted, get contacts getContactsFromDevice(); } else { //required permission not granted, request permission requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, myReqCode); } } else { //permission not required (hahoooo......) getContactsFromDevice(); } } private void getContactsFromDevice() { //Table name in the provider Uri contactUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI; //columns to select String[] projection = new String[]{ ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER}; //criteria for selecting row String selectionClause = null; //arguments String[] selectionArguments = null; //sort order String sortOrder = null; /* Raw Query ContentResolver Query SELECT COLUMN1, COLUMN2 ---> projection FROM table ---> contactUri WHERE COLUMN1 = value ---> selectionClause and selectionArguments ORDER BY COLUMN2 ---> sortOrder */ //contentResolver.query(...) returns cursor object Cursor cursor = getContentResolver().query( contactUri, projection, selectionClause, selectionArguments, sortOrder ); //our contacts string StringBuilder stringBuilder = new StringBuilder(); //check cursor and iterate over if (cursor != null && cursor.moveToFirst()) { while (cursor.moveToNext()) { /* * Name: Gail Powell * Phone: (304) 425-4739 * * Name: Jeanne Malone * Phone: (810) 733-7957 * */ stringBuilder.append("Name: "); stringBuilder.append(cursor.getString(0)); stringBuilder.append("\n"); stringBuilder.append("Phone: "); stringBuilder.append(cursor.getString(1)); stringBuilder.append("\n"); stringBuilder.append("\n"); } //close the cursor object cursor.close(); } else { stringBuilder.append("No contacts in device"); } //set string value in textView binding.contactTextView.setText(stringBuilder); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == myReqCode) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //permission granted, get contacts getContactsFromDevice(); } else { //permission denied, explain.... } } } }
Strings Values:
<resources> <string name="app_name">My Contacts</string> </resources>
6.5 Output
Happy coding!