Content Providers In Android

Android-Content-Providers.png

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?

Working-of-Content-Providers-in-Android.png

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.

Example-of-Content-Providers-in-Android.png

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
content_copy light_mode remove
<?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.

Groovy
content_copy light_mode remove
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.

KotlinJava
content_copy light_mode remove
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

Comparison-of-ContentResolver-query-with-SQL-Query.png

Activity Layout:

XML
content_copy light_mode remove
<?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:

XML
content_copy light_mode remove
<resources>
    <string name="app_name">My Contacts</string>
</resources>

6.5 Output

Happy coding!

Leave a Reply