Working With Content Providers In Android
In a previous article I showed you various aspects of Content Providers in Android. That was a theoretical discussion. This article will follow a more practical approach with enough examples so that you will be able to work comfortably with Content Providers and Content Resolvers. Check out this article for a complete application working with the SQLite database in Android. In this article we are going to use that code as the base and extend that application to make it the provider. We also need another application to be the client and use the content resolver from it.
Getting Started
There are two major IDEs with which you can start your Android development. One is IntelliJ IDEA and the other is Android Studio. Both of these IDEs are from the same company. I like to use Android Studio. If you are not in Android development for a long time then you may be surprised to learn that the Android development plugin for Eclipse IDE is no longer in maintenance.
The Provider Application
We need one application that will act like the data store house. We need an interface for entering some data in there. So, create an android project the way you like and create the user interface (in the land of Android we call it Activity) with the following lines of XML. I am using me.sabuj.sqlitepart1 as the package name and sabuj.me as the domain.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dip">
<EditText
android:id="@+id/editName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
android:hint="Name"
/>
<EditText
android:id="@+id/editEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textEmailAddress"
android:hint="Email"
/>
<EditText
android:id="@+id/editProfession"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="text"
android:hint="Profession" />
<Button
android:id="@+id/save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Save" />
<Button
android:id="@+id/searchByName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Search By Email" />
<TextView
android:id="@+id/textInfo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAlignment="center"
/>
</LinearLayout>
Here is a screenshot of what our app looks like:
SQLite Part1
The SQLite helper extension class called MyDbHelper.java looks like the following:
package me.sabuj.sqlitepart1;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class MyDbHelper extends SQLiteOpenHelper {
public MyDbHelper(Context ctx) {
super(ctx, "db1.sqlite", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
// Create table
db.execSQL("CREATE TABLE persons(id integer primary key autoincrement, name text NOT NULL, email text NOT NULL UNIQUE, profession text NOT NULL)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// We do not want to do anything on upgrade in this application
}
public String save(String name, String email, String profession){
String msg = "";
email = email.toLowerCase(); // Usually emails are case insensitive. So, I am converting it to lowercase to avoid duplication.
ContentValues cvals = new ContentValues();
cvals.put("name", name);
cvals.put("email", email);
cvals.put("profession", profession);
long res = this.getWritableDatabase().insert("persons", null, cvals);
if (res < 0){
msg = "An error occurred";
}else{
msg = "Data inserted Successfully";
}
return msg;
}
public String search(String email){
String msg = "";
email = email.toLowerCase();
Cursor cursor = this.getReadableDatabase().rawQuery("SELECT name, email, profession FROM persons WHERE email='" + email + "'", null);
if (cursor.getCount() < 1){
msg = "Person with email " + email + "NOT FOUND";
}else{
cursor.moveToNext();
msg = "Name: " + cursor.getString(0) + "\n" +
"Email: " + cursor.getString(1) + "\n" +
"Position: " + cursor.getString(2) + "\n";
}
return msg;
}
}
The main activity class called MainActivity.java looks like the following:
package me.sabuj.sqlitepart1;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
MyDbHelper myDbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button saveBtn = (Button) findViewById(R.id.save);
Button searchBtn = (Button) findViewById(R.id.searchByName);
final EditText editName = (EditText) findViewById(R.id.editName);
final EditText editEmail = (EditText) findViewById(R.id.editEmail);
final EditText editProfession = (EditText) findViewById(R.id.editProfession);
final TextView infoView = (TextView) findViewById(R.id.textInfo);
// Preparing the database
myDbHelper = new MyDbHelper(this);
// Setting up click listeners
saveBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String res = myDbHelper.save(
editName.getText().toString(),
editEmail.getText().toString(),
editProfession.getText().toString()
);
infoView.setText(res);
}
});
searchBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String res = myDbHelper.search(editEmail.getText().toString());
infoView.setText(res);
}
});
}
}
The Client Application
To interact with the content provider we need another application. We need to do the CRUD operations to the client application from this one. We need an interface similar to the client application with a few additional buttons for performing the CRUDs. I am using me.sabuj.sandroclient1 as the package name, sabuj.me as the domain and Sandro Client 1 as the application name.
Below is the screenshot of the interface I have designed:
The XML of this activity looks as follows:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dip">
<EditText
android:id="@+id/editName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
android:hint="Name"
/>
<EditText
android:id="@+id/editEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textEmailAddress"
android:hint="Email"
/>
<EditText
android:id="@+id/editProfession"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="text"
android:hint="Profession" />
<Button
android:id="@+id/insert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Insert (C)" />
<Button
android:id="@+id/queryByEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Query (R)" />
<Button
android:id="@+id/updateByEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Update (U)" />
<Button
android:id="@+id/deleteByEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Delete (D)" />
<TextView
android:id="@+id/textInfo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAlignment="center"
/>
</LinearLayout>
The MainActivity.java should look like the following. After creating the click listeners I am keeping them empty to fill in later.
package me.sabuj.contentprovider1;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EditText nameEdit = (EditText) findViewById(R.id.editName);
EditText emailEdit = (EditText) findViewById(R.id.editEmail);
EditText professionEdit = (EditText) findViewById(R.id.editProfession);
TextView textInfo = (TextView) findViewById(R.id.textInfo);
Button insertBtn = (Button) findViewById(R.id.insert);
Button queryByEmailBtn = (Button) findViewById(R.id.queryByEmail);
Button updateByEmailBtn = (Button) findViewById(R.id.updateByEmail);
Button deleteByEmailBtn = (Button) findViewById(R.id.deleteByEmail);
// Setup on click listeners
insertBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
queryByEmailBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
updateByEmailBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
deleteByEmailBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
}
Now, we need to implement the code for those click listeners. Let's get our hands dirty with some real code.
Helper methods: For making our code more DRY, I have created three helper methods on the MainActivity class. The first one collects the texts from the input boxes for Name, Email, and Profession and returns them as an array of strings.
public String[] getInputValues(){
return new String[]{
nameEdit.getText().toString(),
emailEdit.getText().toString().toLowerCase(),
professionEdit.getText().toString()
};
}
The second one returns a ContentValues object by taking the result of the first one.
public ContentValues getContentValues(String[] ip){
ContentValues cv = new ContentValues();
cv.put("name", ip[0]);
cv.put("email", ip[1]);
cv.put("profession", ip[2]);
return cv;
}
The third is just for clearing the input fields on different occasions for providing user with clean input fields to fill up with new ones.
public void clearInputs(){
nameEdit.setText("");
emailEdit.setText("");
professionEdit.setText("");
}
Insert click listener: When the user clicks the insert button, the texts from the input fields are taken and sent to the content provider with the help of content resolver's insert() method. I have coded the content provider's insert() method such that it returns an URI with a -1 appended at the end of it as an insert id, so that we can detect the failure of insertion from the client. The code below explains the idea more.
String[] ip = getInputValues();
Uri uri = getContentResolver().insert(theUri, getContentValues(ip));
if(ContentUris.parseId(uri) == -1){
textInfo.setText("Error occurred: cannot insert the data! It seems like that a person with the same email already exists!");
return;
}
textInfo.setText("Data Inserted:" +
"\nName: " + ip[0] +
"\nEmail: " + ip[1] +
"\nProfession: " + ip[2]
);
clearInputs();
Query click listener: Clicking or touching the query button would execute the following lines of code. When success it will display all three pieces of information of a person in the text box at the bottom of all the buttons. On failure it will display an error message.
String[] ip = getInputValues();
Cursor cursor = getContentResolver().query(theUri, theProjection, "email=?", new String[]{ip[1]}, null);
if(cursor.getCount() == 0){
textInfo.setText("No record with the email address found");
}else {
cursor.moveToFirst();
textInfo.setText("Query Result:" +
"\nName: " + cursor.getString(1) +
"\nEmail: " + cursor.getString(2) +
"\nProfession: " + cursor.getString(3)
);
}
cursor.close();
clearInputs();
Update click listener: To update a person's data we need to identify the person with the email address. To check if a person exists with some particular email address I am performing a query with the help of content resolver and then if a person exists I am updating his other two pieces of data with the update method passing it with the proper id appended to the URI and the email address in the where clause.
String[] ip = getInputValues();
Cursor cursor = getContentResolver().query(theUri, theProjection, "email=?", new String[]{ip[1]}, null);
textInfo.setText("clicked update?");
if(cursor.getCount() == 0){
textInfo.setText("No record with the email address found to delete!");
}else {
cursor.moveToFirst();
getContentResolver().update(ContentUris.withAppendedId(theUri, cursor.getInt(0)), getContentValues(ip),
"email=?", new String[]{cursor.getString(2)});
textInfo.setText("Data Updated For:" +
"\nName: " + cursor.getString(1) + " --> " + ip[0] +
"\nEmail: " + cursor.getString(2) + " --> " + ip[1] +
"\nProfession: " + cursor.getString(3) + " --> " + ip[2]
);
}
cursor.close();
clearInputs();
Delete click listeners: To delete a row we need to take the email address from the email input box and invoke the delete() method on the content resolver with other data presented in the code below:
String[] ip = getInputValues();
long ret = getContentResolver().delete(theUri, "email=?", new String[]{ip[1]});
if(ret <= 0) {
textInfo.setText("The delete operation was not successful!");
}else{
textInfo.setText("Deleted successfully!");
}
clearInputs();
Complete code: Our complete code of the main activity looks like below:
package me.sabuj.sandroclient1;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
public Uri theUri = Uri.parse("content://me.sabuj.sandroprovider1.SANDRO_PROVIDER1/persons");
public EditText nameEdit;
public EditText emailEdit;
public EditText professionEdit;
public String[] theProjection = {"id", "name", "email", "profession"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nameEdit = (EditText) findViewById(R.id.editName);
emailEdit = (EditText) findViewById(R.id.editEmail);
professionEdit = (EditText) findViewById(R.id.editProfession);
final TextView textInfo = (TextView) findViewById(R.id.textInfo);
Button insertBtn = (Button) findViewById(R.id.insert);
Button queryByEmailBtn = (Button) findViewById(R.id.queryByEmail);
Button updateByEmailBtn = (Button) findViewById(R.id.updateByEmail);
Button deleteByEmailBtn = (Button) findViewById(R.id.deleteByEmail);
// Setup on click listeners
insertBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String[] ip = getInputValues();
Uri uri = getContentResolver().insert(theUri, getContentValues(ip));
if(ContentUris.parseId(uri) == -1){
textInfo.setText("Error occurred: cannot insert the data! It seems like that a person with the same email already exists!");
return;
}
textInfo.setText("Data Inserted:" +
"\nName: " + ip[0] +
"\nEmail: " + ip[1] +
"\nProfession: " + ip[2]
);
clearInputs();
}
});
queryByEmailBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String[] ip = getInputValues();
Cursor cursor = getContentResolver().query(theUri, theProjection, "email=?", new String[]{ip[1]}, null);
if(cursor.getCount() == 0){
textInfo.setText("No record with the email address found");
}else {
cursor.moveToFirst();
textInfo.setText("Query Result:" +
"\nName: " + cursor.getString(1) +
"\nEmail: " + cursor.getString(2) +
"\nProfession: " + cursor.getString(3)
);
}
cursor.close();
clearInputs();
}
});
updateByEmailBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Get the row id first
// textInfo.setText("clicked update?");
String[] ip = getInputValues();
Cursor cursor = getContentResolver().query(theUri, theProjection, "email=?", new String[]{ip[1]}, null);
textInfo.setText("clicked update?");
if(cursor.getCount() == 0){
textInfo.setText("No record with the email address found to delete!");
}else {
cursor.moveToFirst();
getContentResolver().update(ContentUris.withAppendedId(theUri, cursor.getInt(0)), getContentValues(ip),
"email=?", new String[]{cursor.getString(2)});
textInfo.setText("Data Updated For:" +
"\nName: " + cursor.getString(1) + " --> " + ip[0] +
"\nEmail: " + cursor.getString(2) + " --> " + ip[1] +
"\nProfession: " + cursor.getString(3) + " --> " + ip[2]
);
}
cursor.close();
clearInputs();
}
});
deleteByEmailBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String[] ip = getInputValues();
long ret = getContentResolver().delete(theUri, "email=?", new String[]{ip[1]});
if(ret <= 0) {
textInfo.setText("The delete operation was not successful!");
}else{
textInfo.setText("Deleted successfully!");
}
clearInputs();
}
});
}
public String[] getInputValues(){
return new String[]{
nameEdit.getText().toString(),
emailEdit.getText().toString().toLowerCase(),
professionEdit.getText().toString()
};
}
public ContentValues getContentValues(String[] ip){
ContentValues cv = new ContentValues();
cv.put("name", ip[0]);
cv.put("email", ip[1]);
cv.put("profession", ip[2]);
return cv;
}
public void clearInputs(){
nameEdit.setText("");
emailEdit.setText("");
professionEdit.setText("");
}
}
The Content Provider
Now, in the provider application we need to create a class that extends ContentProvider named SandroProvider.java. I am setting the authority as me.sabuj.sandroprovider1.SANDRO_PROVIDER1. Initially, the content of this should look like the following:
package me.sabuj.sqlitepart1;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
public class SandroProvider extends ContentProvider {
public SandroProvider() {
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Implement this to handle requests to delete one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public String getType(Uri uri) {
// TODO: Implement this to handle requests for the MIME type of the data
// at the given URI.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO: Implement this to handle requests to insert a new row.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public boolean onCreate() {
// TODO: Implement this to initialize your content provider on startup.
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO: Implement this to handle query requests from clients.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO: Implement this to handle requests to update one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
}
(I took the help of the IDE to generate it automatically.)
Making the provider class is not everything. You need to add the provider to the manifest file. The provider section should look like the following:
<provider
android:name=".SandroProvider"
android:authorities="me.sabuj.sandroprovider1.SANDRO_PROVIDER1"
android:enabled="true"
android:exported="true">
</provider>
Additionally, we can create permission stuffs in the manifest like below:
<permission
android:name="me.sabuj.sandroprovider1.READ_PERMISSION"
android:protectionLevel="normal" />
<permission
android:name="me.sabuj.sandroprovider1.WRITE_PERMISSION"
android:protectionLevel="normal" />
And, add the permissions in the provider block:
<provider
android:name=".SandroProvider"
android:authorities="me.sabuj.sandroprovider1.SANDRO_PROVIDER1"
android:enabled="true"
android:exported="true"
android:grantUriPermissions="true"
android:readPermission="me.sabuj.sandroprovider1.READ_PERMISSION"
android:writePermission="me.sabuj.sandroprovider1.WRITE_PERMISSION"/>
The provider class is really simple if you have properly understood the SQLite mechanisms described in a previous article. It is just the URI matcher and the CRUD method stuffs. The code is more self explanatory:
package me.sabuj.sqlitepart1;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
public class SandroProvider extends ContentProvider {
SQLiteOpenHelper dbHelper;
// authority
public static String AUTORITY = "me.sabuj.sandroprovider1.SANDRO_PROVIDER1";
// paths
public static String PERSONS_PATH = "persons";
public static String SINGLE_PERSON_PATH = "persons/#";
// types
public static final int PERSONS_TYPE = 1;
public static final int SINGLE_PERSON_TYPE = 2;
// MIME TYPES
public static String MIME_PERSONS = ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + "vnd.me.sabuj.sqlitepart1.persons";
public static String MIME_SINGLE_PERSON = ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + "vnd.me.sabuj.sqlitepart1.singleperson";
// The Matcher
public static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static{
uriMatcher.addURI(AUTORITY, PERSONS_PATH, PERSONS_TYPE);
uriMatcher.addURI(AUTORITY, SINGLE_PERSON_PATH, SINGLE_PERSON_TYPE);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return dbHelper.getWritableDatabase().delete("persons", selection, selectionArgs);
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)){
case(PERSONS_TYPE):
return MIME_PERSONS;
case(SINGLE_PERSON_TYPE):
return MIME_SINGLE_PERSON;
default:
return null;
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
long ret = dbHelper.getWritableDatabase().insert("persons", null, values);
Uri insertUri = Uri.parse("content://" + AUTORITY + "/" + PERSONS_PATH + "/" + ret);
return insertUri;
}
@Override
public boolean onCreate() {
dbHelper = new MyDbHelper(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return dbHelper.getWritableDatabase().query("persons", projection, selection, selectionArgs, null, null, sortOrder);
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
return dbHelper.getWritableDatabase().update("persons", values, selection, selectionArgs);
}
}
Complete Project
To help you get the full project of the provider and client application I have created two Github repositories. You need the code from the fourth commit for the provider application and second commit for the client application.
Provider: https://github.com/SabujXi/People-Store/commit/109c4a680663c198e1243d2834283271258515bd
Client: https://github.com/SabujXi/Sandro-Client/commit/41ee9b202a0d33b376450d243190d5d3f146b897
The code may be improved or refactored in future. In future articles on different topics I will use these repositories as a foundation for them. To provide the proper set of code I will provide you with different commit link for different lessons.
Conclusions
Content provider is not only a very important component of Android, but also a bridge that fills the gap of sharing data in a centralized manner keeping data security tight. Also it eliminates the need for a database server systems. A few articles on this does not feel enough to me. But I am not going to write a lot of articles on a single topic. Instead, I am going to bring content providers in whatever other types of lessons we need it. Keep practicing with database in android along with content providers.
Recent Stories
Top DiscoverSDK Experts
Compare Products
Select up to three two products to compare by clicking on the compare icon () of each product.
{{compareToolModel.Error}}
{{CommentsModel.TotalCount}} Comments
Your Comment