Create Custom Content Providers In Android

Create-Own-Content-Providers-In-Android.png

11th February, 2023

Hello Android developer!
In this example you will learn how to create your own Content Provider In Android. Here we are going to create two Android apps. We will add data using the first app and retrieve it using the second app. Let’s get started.

Content-Providers-in-Android-Example-Intro-1.png

1. Create Content Provider

Step 1.1: Create New Project

Create a new project in Android Studio from File ⇒ New Project and select Empty Activity from the templates.

Step 1.2: 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 {
    ...
    ...
    ...
}

Step 1.3: Create New Content Provider In Android Studio

Open the project tool window by clicking on the project option from the Tool window bar. After that select the Android option from the drop-down and open the app → java hierarchy.

Right click on the package name and select New → Other → Content Provider.

Create-New-Content-Provider-In-Android-Studio.png

Fill the required details and click on finish button.

Fill-Details-for-New-Content-Provider.png

Hooray! That’s how easy it is. Alternatively you can create a subclass of Android’s ContentProvider class. Let’s get coding…

Create a private database helper class inside the content provider class. A class that creates and manages a database repository.

KotlinJava
content_copy light_mode remove
class ProductsProvider : ContentProvider() {
 
    companion object {
 
        const val DATABASE_NAME = "ProductDB"
        const val TABLE_NAME = "products"
        var DATABASE_VERSION = 1
    }

    /*
    * Database helper class
    * */
    private class MyProductDatabaseHelper(var context: Context?) :
        SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
 
        //sql query to create a new table
        val CREATE_DB_TABLE =
            (" CREATE TABLE $TABLE_NAME (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price NUMBER NOT NULL);")
 
        override fun onCreate(db: SQLiteDatabase?) {
            db?.execSQL(CREATE_DB_TABLE)
        }
 
        override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
            db?.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
            onCreate(db)
        }
    }
}
class ProductsProvider extends ContentProvider {
    final String DATABASE_NAME = "ProductDB";
    final String TABLE_NAME = "products";
    int DATABASE_VERSION = 1;

    /*
     * Database helper class
     * */
    private class MyProductDatabaseHelper extends SQLiteOpenHelper {
        //sql query to create a new table
        String CREATE_DB_TABLE =
                (" CREATE TABLE " + TABLE_NAME + " (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price NUMBER NOT NULL);");
 
        public MyProductDatabaseHelper(@Nullable Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }
 
        @Override
        public void onCreate(SQLiteDatabase database) {
            database.execSQL(CREATE_DB_TABLE);
        }
 
        @Override
        public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {
            database.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
            onCreate(database);
        }
    }
}

Now add functionality to each content provider methods. Like query, insert, update, delete.

KotlinJava
content_copy light_mode remove
package com.androidchunk.contentprovider_admin

import android.content.*
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteException
import android.database.sqlite.SQLiteOpenHelper
import android.database.sqlite.SQLiteQueryBuilder
import android.net.Uri

class ProductsProvider : ContentProvider() {

    companion object {
        const val PROVIDER_NAME = "com.demo.product.provider"
        const val URL = "content://$PROVIDER_NAME/products"
        val CONTENT_URI = Uri.parse(URL)

        private val values: HashMap<String, String>? = null

        private var db: SQLiteDatabase? = null

        const val id = "id"
        const val name = "name"
        const val price = "price"
        const val uriCode = 1
        var uriMatcher: UriMatcher? = null

        const val DATABASE_NAME = "ProductDB"
        const val TABLE_NAME = "products"
        var DATABASE_VERSION = 1

        init {
            uriMatcher = UriMatcher(UriMatcher.NO_MATCH)

            uriMatcher!!.addURI(PROVIDER_NAME, "products", uriCode)

            // to access a particular row
            // of the table
            uriMatcher!!.addURI(
                PROVIDER_NAME,
                "products/*",
                uriCode
            )
        }
    }

    override fun onCreate(): Boolean {
        val dbHelper = MyProductDatabaseHelper(context)
        db = dbHelper.writableDatabase
        return if (db != null) {
            true
        } else false
    }

    override fun query(
        uri: Uri, projection: Array<String>?, selection: String?,
        selectionArgs: Array<String>?, sortOrder: String?
    ): Cursor? {
        var sortOrder = sortOrder
        val queryBuilder = SQLiteQueryBuilder()
        queryBuilder.tables = TABLE_NAME

        when (uriMatcher?.match(uri)) {
            uriCode -> queryBuilder.projectionMap = values
            else -> throw IllegalArgumentException("Unknown URI $uri")
        }
        if (sortOrder == null || sortOrder == "") {
            sortOrder = id
        }

        val cursor =
            queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder)

        cursor.setNotificationUri(context?.contentResolver, uri)
        return cursor
    }


    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        val rowID = db?.insert(TABLE_NAME, "", values)
        if (rowID!! > 0) {
            val tempUri = ContentUris.withAppendedId(CONTENT_URI, rowID)
            context!!.contentResolver.notifyChange(tempUri, null)
            return tempUri
        }
        throw SQLiteException("Failed to add a record into $uri")
    }

    override fun update(
        uri: Uri, values: ContentValues?, selection: String?,
        selectionArgs: Array<String>?
    ): Int {
        var count = 0
        when (uriMatcher?.match(uri)) {
            uriCode ->
                count = db!!.update(TABLE_NAME, values, selection, selectionArgs)
            else -> throw IllegalArgumentException("Unknown URI $uri")
        }
        context?.contentResolver?.notifyChange(uri, null)
        return count
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
        var count = 0
        when (uriMatcher?.match(uri)) {
            uriCode ->
                count = db!!.delete(TABLE_NAME, selection, selectionArgs)
            else -> throw IllegalArgumentException("Unknown URI $uri")
        }
        context?.contentResolver?.notifyChange(uri, null)
        return count
    }

    override fun getType(uri: Uri): String? {
        return when (uriMatcher?.match(uri)) {
            uriCode ->
                "vnd.android.cursor.dir/products"
            else -> {
                throw IllegalArgumentException("Unsupported URI: $uri")
            }
        }
    }

    /*
    * Database helper class
    * */
    private class MyProductDatabaseHelper(var context: Context?) :
        SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {

        //sql query to create a new table
        val CREATE_DB_TABLE =
            (" CREATE TABLE $TABLE_NAME (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price NUMBER NOT NULL);")

        override fun onCreate(db: SQLiteDatabase?) {
            db?.execSQL(CREATE_DB_TABLE)
        }

        override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
            db?.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
            onCreate(db)
        }
    }
}
package com.androidchunk.contentprovider_admin;

import android.content.*;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.HashMap;


class ProductsProvider extends ContentProvider {

    static final String PROVIDER_NAME = "com.demo.product.provider";
    static final String URL = "content://" + PROVIDER_NAME + "/products";
    static Uri CONTENT_URI = Uri.parse(URL);

    private HashMap<String, String> values = null;

    private SQLiteDatabase db = null;

    static final String id = "id";
    static final String name = "name";
    static final String price = "price";
    static final int uriCode = 1;
    UriMatcher uriMatcher = null;

    final String DATABASE_NAME = "ProductDB";
    final String TABLE_NAME = "products";
    int DATABASE_VERSION = 1;

    public ProductsProvider() {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        uriMatcher.addURI(PROVIDER_NAME, "products", uriCode);

        // to access a particular row
        // of the table
        uriMatcher.addURI(
                PROVIDER_NAME,
                "products/*",
                uriCode
        );
    }

    @Override
    public boolean onCreate() {
        MyProductDatabaseHelper dbHelper = new MyProductDatabaseHelper(getContext());
        db = dbHelper.getWritableDatabase();
        if (db != null) {
            return true;
        } else return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
        queryBuilder.setTables(TABLE_NAME);

        if (uriMatcher.match(uri) == uriCode) {
            queryBuilder.setProjectionMap(values);
        } else {
            throw new IllegalArgumentException("Unknown URI " + uri);
        }

        if (sortOrder == null || sortOrder.equals("")) {
            sortOrder = id;
        }

        Cursor cursor =
                queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);

        cursor.setNotificationUri(getContext().getContentResolver(), uri);
        return cursor;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        long rowID = db.insert(TABLE_NAME, "", contentValues);
        if (rowID > 0) {
            Uri tempUri = ContentUris.withAppendedId(CONTENT_URI, rowID);
            getContext().getContentResolver().notifyChange(tempUri, null);
            return tempUri;
        }
        throw new SQLiteException("Failed to add a record into " + uri);
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String selection, @Nullable String[] selectionArgs) {
        int count;
        if (uriMatcher.match(uri) == uriCode) {
            count = db.update(TABLE_NAME, contentValues, selection, selectionArgs);
        } else {
            throw new IllegalArgumentException("Unknown URI " + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        int count;
        if (uriMatcher.match(uri) == uriCode) {
            count = db.delete(TABLE_NAME, selection, selectionArgs);
        } else {
            throw new IllegalArgumentException("Unknown URI " + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        if (uriMatcher.match(uri) == uriCode) {
            return "vnd.android.cursor.dir/products";
        } else {
            throw new IllegalArgumentException("Unsupported URI: " + uri);
        }
    }

    /*
     * Database helper class
     * */
    private class MyProductDatabaseHelper extends SQLiteOpenHelper {
        //sql query to create a new table
        String CREATE_DB_TABLE =
                (" CREATE TABLE " + TABLE_NAME + " (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price NUMBER NOT NULL);");

        public MyProductDatabaseHelper(@Nullable Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase database) {
            database.execSQL(CREATE_DB_TABLE);
        }

        @Override
        public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {
            database.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
            onCreate(database);
        }
    }
}

Step 1.4: Add Android Content Provider in Androidmanifest.xml

We have to add the Content Provider to the Androidmanifest.xml file. Android Studio has made this task easy. Android Studio has already registered the Content Provider in the Androidmanifest.xml file.

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">
   
    <application>
         ...
         ...
   
        <provider
            android:name=".ProductsProvider"
            android:authorities="com.demo.product.provider"
            android:enabled="true"
            android:exported="true" />
   
        <activity/>
         ...
         ...
    </application>
   
</manifest>

Step 1.5: Update User Interface

To add data, we have created a simple User Interface (UI) as follows.

XML
content_copy light_mode remove
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="35dp"
        android:orientation="vertical"
        android:layout_marginStart="15dp"
        android:layout_marginEnd="15dp">

        <EditText
            android:id="@+id/productNameEditText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="@string/enter_product_name"
            android:inputType="textPersonName"
            tools:ignore="Autofill" />

        <EditText
            android:id="@+id/productPriceEditText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:ems="10"
            android:hint="@string/enter_product_price"
            android:inputType="numberDecimal"
            tools:ignore="Autofill" />

        <Button
            android:id="@+id/addProductButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="15dp"
            android:textColor="@color/white"
            android:textAlignment="center"
            android:background="@color/purple_700"
            android:text="@string/add_new_product" />

    </LinearLayout>
</RelativeLayout>

Step 1.6: Update Main Activity

Now open MainActivity and add button click listener. See the given code.

KotlinJava
content_copy light_mode remove
package com.androidchunk.contentprovider_admin

import android.content.ContentValues
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import com.androidchunk.contentprovider_admin.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    //view binding
    lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        //add product button click event handling
        binding.addProductButton.setOnClickListener {
            //get product name and price
            val productName = binding.productNameEditText.text.toString()
            val productPrice = binding.productPriceEditText.text.toString()

            if (productName.isNotEmpty() && productPrice.isNotEmpty()) {
                //Key-value object to add value in the database
                val values = ContentValues()
                values.put(ProductsProvider.name, productName)
                values.put(ProductsProvider.price, productPrice)

                //insert data using Content URI
                val uri = contentResolver.insert(ProductsProvider.CONTENT_URI, values)
                Toast.makeText(this, uri.toString(), Toast.LENGTH_SHORT).show()
            }else{
                Toast.makeText(this, "Please fill all fields", Toast.LENGTH_SHORT).show()
            }
        }
    }
}
package com.androidchunk.contentprovider_admin;

import android.content.ContentValues;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.androidchunk.contentprovider_admin.databinding.ActivityMainBinding;

class MainActivity extends AppCompatActivity {
    //view binding
    ActivityMainBinding binding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        //add product button click event handling
        binding.addProductButton.setOnClickListener(view -> {
            //get product name and price
            String productName = binding.productNameEditText.getText().toString();
            String productPrice = binding.productPriceEditText.getText().toString();

            if (!productName.isEmpty() && !productPrice.isEmpty()) {
                //Key-value object to add value in the database
                ContentValues values =new  ContentValues();
                values.put(ProductsProvider.name, productName);
                values.put(ProductsProvider.price, productPrice);

                //insert data using Content URI
                Uri uri = getContentResolver().insert(ProductsProvider.CONTENT_URI, values);
                Toast.makeText(this, uri.toString(), Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "Please fill all fields", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

Strings Values

XML
content_copy light_mode remove
<resources>
    <string name="app_name">MyContentProvider-ADMIN</string>
    <string name="enter_product_name">Enter Product Name</string>
    <string name="enter_product_price">Enter Product Price</string>
    <string name="add_new_product">Add New Product</string>
</resources>

That’s it, the Custom Android Content Provider is ready. Run the app and add some data.

2. Get Data using Content Provider In Android

Step 2.1: Create New Project

Create a new project in Android Studio from File ⇒ New Project and select Empty Activity from the templates.

Step 2.2: 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 {
    ...
    ...
    ...
}

Step 2.3: Update User Interface

We have taken a textView which will display the product data on clicking the refresh button.

XML
content_copy light_mode remove
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">

    <Button
        android:id="@+id/refreshButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="@color/teal_700"
        android:text="@string/refresh_products"
        android:textColor="@color/white" />
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@id/refreshButton">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/myTextView"
                android:layout_width="wrap_content"
                android:textColor="@color/black"
                android:layout_height="wrap_content" />
        </LinearLayout>

    </ScrollView>
</RelativeLayout>

Step 2.4: Update MainActivity

Open the main activity and add the following code and run the app.

KotlinJava
content_copy light_mode remove
package com.androidchunk.contentprovidercustomer

import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.androidchunk.contentprovidercustomer.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        binding = ActivityMainBinding.inflate(layoutInflater)
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.refreshButton.setOnClickListener {

            //Table name in the provider
            val contactUri = Uri.parse("content://com.demo.product.provider/products")

            //columns to select
            val projection = null

            //criteria for selecting row
            val selectionClause = null
            //arguments
            val selectionArguments = null
            //sort order
            val sortOrder = null
            //query
            val cursor = contentResolver.query(
                contactUri,
                projection,
                selectionClause,
                selectionArguments,
                sortOrder
            )

            //our product data string
            val stringBuilder = StringBuilder()

            //check cursor and iterate over
            if (cursor != null && cursor.moveToFirst()) {
                do {
                    stringBuilder.append("ID: ")
                    stringBuilder.append(cursor.getString(cursor.getColumnIndexOrThrow("id")))

                    stringBuilder.append("\t")
                    stringBuilder.append("\t")

                    stringBuilder.append("Name: ")
                    stringBuilder.append(cursor.getString(cursor.getColumnIndexOrThrow("name")))

                    stringBuilder.append("\t")
                    stringBuilder.append("\t")

                    stringBuilder.append("Price: ")
                    stringBuilder.append(cursor.getString(cursor.getColumnIndexOrThrow("price")))

                    stringBuilder.append("\n")
                    stringBuilder.append("\n")

                } while (cursor.moveToNext())
                //close the cursor object
                cursor.close()
            } else {
                stringBuilder.append("Noting to show")
            }
            //set string value in  textView
            binding.myTextView.text = stringBuilder
        }
    }
}
package com.androidchunk.contentprovidercustomer;

import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.androidchunk.contentprovidercustomer.databinding.ActivityMainBinding;

class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        super.onCreate(savedInstanceState);
        setContentView(binding.getRoot());

        binding.refreshButton.setOnClickListener(view -> {
            //Table name in the provider
            Uri contactUri = Uri.parse("content://com.demo.product.provider/products");

            //columns to select
            String[] projection = null;

            //criteria for selecting row
            String selectionClause = null;
            //arguments
            String[] selectionArguments = null;
            //sort order
            String sortOrder = null;
            //query
            Cursor cursor = getContentResolver().query(
                    contactUri,
                    projection,
                    selectionClause,
                    selectionArguments,
                    sortOrder
            );

            //our product data string
            StringBuilder stringBuilder = new StringBuilder();

            //check cursor and iterate over
            if (cursor != null && cursor.moveToFirst()) {
                do {
                    stringBuilder.append("ID: ");
                    stringBuilder.append(cursor.getString(cursor.getColumnIndexOrThrow("id")));

                    stringBuilder.append("\t");
                    stringBuilder.append("\t");

                    stringBuilder.append("Name: ");
                    stringBuilder.append(cursor.getString(cursor.getColumnIndexOrThrow("name")));

                    stringBuilder.append("\t");
                    stringBuilder.append("\t");

                    stringBuilder.append("Price: ");
                    stringBuilder.append(cursor.getString(cursor.getColumnIndexOrThrow("price")));

                    stringBuilder.append("\n");
                    stringBuilder.append("\n");

                } while (cursor.moveToNext());
                //close the cursor object
                cursor.close();
            } else {
                stringBuilder.append("Noting to show");
            }
            //set string value in  textView
            binding.myTextView.setText(stringBuilder);
        });
    }
}

Strings Values

XML
content_copy light_mode remove
<resources>
    <string name="app_name">Content Provider Customer</string>
    <string name="refresh_products">Refresh Products</string>
</resources>

3. Output

Happy coding!

Leave a Reply