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.
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.
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.
Fill the required details and click on finish button.
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.
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.
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 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 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.
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
<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.
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 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.
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
<resources> <string name="app_name">Content Provider Customer</string> <string name="refresh_products">Refresh Products</string> </resources>
3. Output
Happy coding!