In this post, I will present the way to customizing
ContentProvider
to loading data from a SQLite database, this can reduce data loading time. In order to prepare well for the understanding of this issue, you can read the previous post about use SQLite database in Android.DEMO VIDEO:
Creating a database class
SQLiteOpenHelper
. So, make a simple subclass and overriding onCreate()
and onUpgrade()
like this:
DBHelper.java
package info.devexchanges.contentproviderwithsqlitedb;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DBHelper extends SQLiteOpenHelper {
private static final int DB_VERSION = 1;
private static final String DB_NAME = "db_friend";
public static final String TABLE_FRIENDS = "friend";
public static final String ID = "id";
public static final String COL_NAME = "name";
public static final String COL_JOB = "job";
private static final String CREATE_TABLE_FRIENDS = "create table " + TABLE_FRIENDS
+ " (" + ID + " integer primary key autoincrement, " + COL_NAME
+ " text not null, " + COL_JOB + " text not null);";
public DBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_FRIENDS);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_FRIENDS);
onCreate(db);
}
}
Building Content Provider
ContentProvider
, we have to override these require methods:onCreate()
:which is called to initialize the provider. Only called from the application main thread, and must avoid performing lengthy operations, other methods below didn't.query()
: which returns data to the caller.insert()
: which inserts new data into the content provider.delete()
: which deletes data from the content provider.update()
: which updates existing data in the content provider.getType()
: which returns the MIME type of data in the content provider.
onCreate()
method will be like:
@Override
public boolean onCreate() {
dbHelper = new DBHelper(getContext());
// permissions to be writable
database = dbHelper.getWritableDatabase();
if (database == null)
return false;
else
return true;
}
Content providers work with data at the URI
level. For instance, this URI
identifies all of the records:
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + FRIENDS_BASE_PATH);
As you can see, we must provide an authority for any custom content provider as the name of the content provider. In this example, it is:
private static final String AUTHORITY = "info.devexchanges.contentprovider.CustomContentProvider";
In query()
method, for getting data from database, we must call getReadableDatabase()
method of SQLiteOpenHelper
before start query by using Cursor
. On the contrary, deleting, updating, inserting need getWritableDatabase()
. These changing data methods use ContentResolver
to access and alter data. Full code for this content provider, after overriding these important methods:
CustomContentProvider.java
package info.devexchanges.contentproviderwithsqlitedb;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.text.TextUtils;
public class CustomContentProvider extends ContentProvider {
private DBHelper dbHelper;
private SQLiteDatabase database;
private static final String AUTHORITY = "info.devexchanges.contentprovider.CustomContentProvider";
public static final int FRIENDS = 100;
public static final int FRIEND_ID = 110;
private static final String FRIENDS_BASE_PATH = "friend";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + FRIENDS_BASE_PATH);
public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/mt-tutorial";
public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/mt-tutorial";
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(AUTHORITY, FRIENDS_BASE_PATH, FRIENDS);
uriMatcher.addURI(AUTHORITY, FRIENDS_BASE_PATH + "/#", FRIEND_ID);
}
@Override
public boolean onCreate() {
dbHelper = new DBHelper(getContext());
// permissions to be writable
database = dbHelper.getWritableDatabase();
if (database == null)
return false;
else
return true;
}
@SuppressWarnings("ConstantConditions")
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(DBHelper.TABLE_FRIENDS);
int uriType = uriMatcher.match(uri);
switch (uriType) {
case FRIEND_ID:
queryBuilder.appendWhere(DBHelper.ID + "=" + uri.getLastPathSegment());
break;
case FRIENDS:
break;
default:
throw new IllegalArgumentException("Unknown URI");
}
Cursor cursor = queryBuilder.query(dbHelper.getReadableDatabase(),
projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Nullable
@Override
public String getType(Uri uri) {
return null;
}
@SuppressWarnings("ConstantConditions")
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
long row = database.insert(DBHelper.TABLE_FRIENDS, "", values);
// If record is added successfully
if (row > 0) {
Uri newUri = ContentUris.withAppendedId(CONTENT_URI, row);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
throw new SQLException("Fail to add a new record into " + uri);
}
@SuppressWarnings("ConstantConditions")
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int uriType = uriMatcher.match(uri);
int rowsAffected = 0;
switch (uriType) {
case FRIENDS:
rowsAffected = database.delete(DBHelper.TABLE_FRIENDS, selection, selectionArgs);
break;
case FRIEND_ID:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsAffected = database.delete(DBHelper.TABLE_FRIENDS, DBHelper.ID + "=" + id, null);
} else {
rowsAffected = database.delete(DBHelper.TABLE_FRIENDS, selection + " and " + DBHelper.ID + "=" + id, selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown or Invalid URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsAffected;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
Registering in AndroidManifest
AndroidManifest.xml
with <provider>
tag:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="info.devexchanges.contentproviderwithsqlitedb">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:multiprocess="true"
android:authorities="info.devexchanges.contentprovider.CustomContentProvider"
android:name="info.devexchanges.contentproviderwithsqlitedb.CustomContentProvider" />
</application>
</manifest>
Usage on User Interface
Activity
which show all table records by a ListView
and in it, you can add a new record to database table or remove any item from database by clicking delete button on each row. All access from UI to database through content provider URI
.About adding a new record, you can use
ContentValues
by this code:
//Adding new record to database with Content provider
ContentValues values = new ContentValues();
values.put(DBHelper.COL_NAME, getText(txtName));
values.put(DBHelper.COL_JOB, getText(txtJob));
getContentResolver().insert(CustomContentProvider.CONTENT_URI, values);
Fetching all records from table with ContentResolver
and Cursor
and store them to an ArrayList
:
Cursor cursor = getContentResolver().query(CustomContentProvider.CONTENT_URI, null, null, null, null);
if (!cursor.moveToFirst()) {
Toast.makeText(this, " no record yet!", Toast.LENGTH_SHORT).show();
} else {
do {
String name = cursor.getString(cursor.getColumnIndex(DBHelper.COL_NAME));
String job = cursor.getString(cursor.getColumnIndex(DBHelper.COL_JOB));
//Loading to arraylist to set adapter data for ListView
Friend friend = new Friend(name, job);
friendList.add(friend);
} while (cursor.moveToNext());
}
And deleting an exist record:
//deleting a record in database table based on "name"
String selection = DBHelper.COL_NAME + " = \"" + friend.getName() + "\"";
int rowsDeleted = activity.getContentResolver().delete(CustomContentProvider.CONTENT_URI, selection, null);
if (rowsDeleted > 0) {
Toast.makeText(activity, "Deleted!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
Full code of this Activity
:
MainActivity.java
It's layout (only contains a package info.devexchanges.contentproviderwithsqlitedb;
import android.app.Dialog;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ListView listView;
private List<Friend> friendList;
private ListViewAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list);
friendList = new ArrayList<>();
//set Listview adapter
adapter = new ListViewAdapter(this, R.layout.item_listview, friendList);
listView.setAdapter(adapter);
showAllFriends();
}
public void showAllFriends() {
friendList.clear(); //clear old arraylist data first
Cursor cursor = getContentResolver().query(CustomContentProvider.CONTENT_URI, null, null, null, null);
if (!cursor.moveToFirst()) {
Toast.makeText(this, " no record yet!", Toast.LENGTH_SHORT).show();
} else {
do {
String name = cursor.getString(cursor.getColumnIndex(DBHelper.COL_NAME));
String job = cursor.getString(cursor.getColumnIndex(DBHelper.COL_JOB));
//Loading to arraylist to set adapter data for ListView
Friend friend = new Friend(name, job);
friendList.add(friend);
} while (cursor.moveToNext());
}
adapter.notifyDataSetChanged();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add:
showAddingDialog();
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
private void showAddingDialog() {
// custom dialog
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.layout_dialog_add);
dialog.setTitle("Adding a new friend");
final EditText txtName = (EditText) dialog.findViewById(R.id.name);
final EditText txtJob = (EditText) dialog.findViewById(R.id.job);
Button btnAdd = (Button) dialog.findViewById(R.id.btn_ok);
btnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!hasText(txtJob) || !hasText(txtName)) {
Toast.makeText(getBaseContext(), "Please input full information...", Toast.LENGTH_SHORT).show();
} else {
//Adding new record to database with Content provider
// Add a new birthday record
ContentValues values = new ContentValues();
values.put(DBHelper.COL_NAME, getText(txtName));
values.put(DBHelper.COL_JOB, getText(txtJob));
getContentResolver().insert(CustomContentProvider.CONTENT_URI, values);
Toast.makeText(getBaseContext(), "Inserted!", Toast.LENGTH_SHORT).show();
//reloading data
showAllFriends();
//dismiss dialog after adding process
dialog.dismiss();
}
}
});
dialog.show();
}
private boolean hasText(TextView textView) {
if (textView.getText().toString().trim().equals("")) {
return false;
} else return true;
}
private String getText(TextView textView) {
return textView.getText().toString().trim();
}
}
ListView
):
activity_main.xml
Customizing a <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
ListView
adapter based on ArrayAdapter
(the deletion code I put here):
ListViewAdapter.java
package info.devexchanges.contentproviderwithsqlitedb;
import android.app.Activity;
import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
public class ListViewAdapter extends ArrayAdapter<Friend> {
private MainActivity activity;
public ListViewAdapter(MainActivity context, int resource, List<Friend> objects) {
super(context, resource, objects);
this.activity = context;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_listview, parent, false);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
//set data to views
holder.job.setText(getItem(position).getJob());
holder.name.setText(getItem(position).getName());
holder.btnDel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showAlertDialog(getItem(position));
}
});
return convertView;
}
public void showAlertDialog(final Friend friend) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle("Delete Confirm");
builder.setCancelable(true);
builder.setMessage("Are you sure?");
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//deleting a record in database table based on "name"
String selection = DBHelper.COL_NAME + " = \"" + friend.getName() + "\"";
int rowsDeleted = activity.getContentResolver().delete(CustomContentProvider.CONTENT_URI, selection, null);
if (rowsDeleted > 0) {
Toast.makeText(activity, "Deleted!", Toast.LENGTH_SHORT).show();
//reloading data
activity.showAllFriends();
} else {
Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
}
}
});
builder.setNegativeButton("Cancel", null);
builder.show();
}
private class ViewHolder {
private TextView name;
private TextView job;
private ImageView btnDel;
public ViewHolder(View v) {
name = (TextView) v.findViewById(R.id.name);
job = (TextView) v.findViewById(R.id.job);
btnDel = (ImageView) v.findViewById(R.id.btn_del);
}
}
}
Some necessary files
ListView
item:
item_listview.xml
The POJO class for this project:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textColor="@android:color/holo_blue_dark"
android:textStyle="bold" />
<TextView
android:id="@+id/job"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/name"
android:text="@string/app_name"
android:textColor="@android:color/holo_green_dark"
android:textStyle="italic" />
<ImageView
android:id="@+id/btn_del"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:contentDescription="@string/app_name"
android:src="@drawable/delete" />
</RelativeLayout>
Friend.java
A dialog layout, use in adding the new record above:
package info.devexchanges.contentproviderwithsqlitedb;
public class Friend {
private String name;
private String job;
public Friend(String name, String job) {
this.name = name;
this.job = job;
}
public String getName() {
return name;
}
public String getJob() {
return job;
}
}
layout_dialog_add.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/activity_horizontal_margin">
<EditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Put a name"
android:inputType="textPersonName" />
<EditText
android:id="@+id/job"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Put a job"
android:inputType="textPersonName" />
<Button
android:id="@+id/btn_ok"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add" />
</LinearLayout>
Run the application
When user click on (+) button, a dialog will appear to input the new record information:
And this is the
Toast
notice after delete a record successful:Conclusions
ListView
control. In this, I have not provide the way to update (edit) a record in database table, readers can find out yourself!
I hope you’ve enjoyed this tutorial and finally, you can get full project code on @Github by click the button below.