Membuat Content Provider Sendiri

Membuat 'content provider' kita sendiri di Android cukup mudah. Yang kita perlukan hanya melakukan 'extend' class abstract 'ContentProvider' dan melakukan 'override' pada berbagai method yang ada di dalamnya.

Pada bagian ini, kita akan mencoba berlatih membuat 'content provider' sederhana yang menyimpan daftar buku-buku. Untuk sekedar ilustrasi, 'content provider' yang akan menyimpan buku-buku di tabel database yang berisi tiga kolom seperti pada gambar di bawah ini.


Latihan membuat content provider sendiri

1. Kita buat project di Android Studio dengan nama: Content Provider

2. Kita buat satu file class java dengan nama BooksProvider.java
package com.example.contentprovider;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;

public class BooksProvider extends ContentProvider {
static final String PROVIDER_NAME = "com.example.provider.Books";

static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/books");

static final String _ID = "_id";
static final String TITLE = "title";
static final String ISBN = "isbn";

static final int BOOKS = 1;
static final int BOOK_ID = 2;

private static final UriMatcher uriMatcher;
static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "books", BOOKS);
uriMatcher.addURI(PROVIDER_NAME, "books/#", BOOK_ID);
}

/* --untuk penggunaan database--*/
SQLiteDatabase booksDB;
static final String DATABASE_NAME = "Books";
static final String DATABASE_TABLE = "titles";
static final int DATABASE_VERSION = 1;
static final String DATABASE_CREATE =
"create table " + DATABASE_TABLE +
" (_id integer primary key autoincrement, "
+ "title text not null, isbn text not null);";

private static class DatabaseHelper extends SQLiteOpenHelper
{
DatabaseHelper (Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}

@Override
public void onCreate(SQLiteDatabase db)
{
db.execSQL(DATABASE_CREATE);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w("Content provider db", "Upgrading database from version " +
oldVersion + " to " + newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS titles");
onCreate(db);
}
}

@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
// arg0 = uri
// arg1 = selection
/ /arg2 = selectionArgs

int count = 0;
switch (uriMatcher.match(arg0)) {
case BOOKS:
count = booksDB.delete(DATABASE_TABLE, arg1, arg2);
break;
case BOOK_ID:
String id = arg0.getPathSegments().get(1);
count = booksDB.delete(DATABASE_TABLE, _ID +
" = " + id + (!TextUtils.isEmpty(arg1) ? " AND (" +
arg1 + ')' : ""), arg2);
break;
default:
throw new IllegalArgumentException("Unknown URI " + arg0);
}
getContext().getContentResolver().notifyChange(arg0, null);
return count;
}

@Override
public String getType(Uri uri){
switch (uriMatcher.match(uri)) {
/*-- mengambil semua buku --*/
case BOOKS:
return "vnd.android.cursor.dir/vnd.com.example.books ";

/*-- mengambil buku tertentu --*/
case BOOK_ID:
return "vnd.android.cursor.item/vnd.com.eample.books";
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}

@Override
public Uri insert(Uri uri, ContentValues values){
/* --add a new book--*/
long rowID = booksDB.insert(DATABASE_TABLE, "", values);

/*-- bila berhasil ditambahkan--*/
if (rowID>0)
{
Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID);
getContext().getContentResolver().notifyChange(_uri, null);
return _uri;
}
throw new SQLException("Failed to insert row into " + uri);
}

@Override
public boolean onCreate() {
Context context = getContext();
DatabaseHelper dbHelper = new DatabaseHelper(context);
booksDB = dbHelper.getWritableDatabase();
return (booksDB == null) ? false:true;
}

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

if (uriMatcher.match(uri) == BOOK_ID)
/*--bila mendapatkan buku tertentu--*/
sqlBuilder.appendWhere(_ID + " = " + uri.getPathSegments().get(1));

if (sortOrder == null || sortOrder == "")
sortOrder = TITLE;

Cursor c = sqlBuilder.query(
booksDB,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);

/*--me-register untuk mengamati URI bila ada perubahan--*/
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}

@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
switch (uriMatcher.match(uri)) {
case BOOKS:
count = booksDB.update(DATABASE_TABLE, values, selection, selectionArgs);
break;
case BOOK_ID:
count = booksDB.update(DATABASE_TABLE, values,
_ID + " = " + uri.getPathSegments().get(1) +
(!TextUtils.isEmpty(selection) ? " AND (" +
selection + ')' : ""), selectionArgs);
break;
default: throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
}
3. Kita tambahkan kode yang ditulis tebal dalam file AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.contentprovider">
<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:name="BooksProvider" android:authorities="com.example.provider.Books">
</provider>

</application>
</manifest>

Penjelasan:

Dalam contoh latihan ini, kita terlebih dahulu membuat class java yang kita beri nama 'BooksProvider' yang menerapkan 'extends' class 'ContentProvider'. Method-method yang akan di-override dalam class ini adalah sebagai berikut:

  • getType() : method ini akan mengembalikan/menghasilkan jenis data MIME pada URI yang diberikan.
  • onCreate() : method ini dipanggil ketika 'provider' dijalankan.
  • query() : method ini untuk menerima 'request' dari 'client'. Hasil yang dikembalikan akan berupa object 'Cursor'.
  • insert() : method ini akan men-insert baris/data baru di 'content provider'.
  • delete() : method ini akan men-delete baris/data yang ada di 'content provider'.
  • update() : methode ini akan men-update baris/data yang ada di 'content provider'.
Di dalam 'content provider' kita, kita bebas memilih bagaimana data akan disimpan -- bisa di file, XML, database, atau bahkan melalui web services. Dalam latihan ini, kita menggunakan database SQLite yang sudah dibahas dalam latihan sebelumnya (Menyimpan Data ke Database).

Berikutnya kita men-definisikan berbagai konstanta di dalam class 'BooksProvider' seperti berikut:
static final String PROVIDER_NAME = "com.example.provider.Books";

static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/books");

static final String _ID = "_id";
static final String TITLE = "title";
static final String ISBN = "isbn";

static final int BOOKS = 1;
static final int BOOK_ID = 2;

private static final UriMatcher uriMatcher;
static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "books", BOOKS);
uriMatcher.addURI(PROVIDER_NAME, "books/#", BOOK_ID);
}

/* --untuk penggunaan database--*/
SQLiteDatabase booksDB;
static final String DATABASE_NAME = "Books";
static final String DATABASE_TABLE = "titles";
static final int DATABASE_VERSION = 1;
static final String DATABASE_CREATE =
"create table " + DATABASE_TABLE +
" (_id integer primary key autoincrement, "
+ "title text not null, isbn text not null);";

Perhatikan bahwa di dalam kode tersebut kita menggunakan object 'UriMatcher' untuk melakukan parsing URI yang dilewatkan ke 'content provider' melalui 'ContentResolver'. Contohnya, URI berikut ini akan me-request semua buku yang ada di dalam 'content provider' :
content://com.example.provider.Books/books
Sedangkan yang berikut ini akan me-request buku tertentu dengab _id 5:
content://com.example.provider.Books/books/5
Berikutnya, 'content provider' kita akan menggunakan database SQLite untuk menyimpan daftar buku-buku. Perhatikan, disini kita akan menggunakan class yang berfungsi untuk bantuan yaitu SQLiteOpenHelper untuk mempermudah dalam mengelola database:
private static class DatabaseHelper extends SQLiteOpenHelper
{
DatabaseHelper (Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}

@Override
public void onCreate(SQLiteDatabase db)
{
db.execSQL(DATABASE_CREATE);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w("Content provider db", "Upgrading database from version " +
oldVersion + " to " + newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS titles");
onCreate(db);
}
}
Berikutnya, kita me-override method 'getType()' untuk mendeskripsikan tipe data untuk 'content provider' kita. Dengan menggunakan object 'UriMatcher', method ini akan mengembalikan/menghasilkan 'vnd.android.cursor.dir/vnd.com.example.books' untuk lebih dari satu buku, dan 'vnd.android.cursor.item/vnd.com.eample.books' untuk satu buku:
@Override
public String getType(Uri uri){
switch (uriMatcher.match(uri)) {
/*-- mengambil semua buku --*/
case BOOKS:
return "vnd.android.cursor.dir/vnd.com.example.books";

/*--mengambil buku tertentu--*/
case BOOK_ID:
return "vnd.android.cursor.item/vnd.com.eample.books";
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
Berikutnya, kita me-override method 'onCreate()' untuk membuka koneksi ke database ketika 'content provider' dijalankan/diaktifkan:
@Override
public boolean onCreate() {
Context context = getContext();
DatabaseHelper dbHelper = new DatabaseHelper(context);
booksDB = dbHelper.getWritableDatabase();
return (booksDB == null) ? false:true;
}
Kita me-override method 'query()' supaya client bisa melakukan query terhadap buku-buku yang di 'content provider':
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
sqlBuilder.setTables(DATABASE_TABLE);

if (uriMatcher.match(uri) == BOOK_ID)
/*--bila mengambil buku tertentu--*/
sqlBuilder.appendWhere(_ID + " = " + uri.getPathSegments().get(1));

if (sortOrder == null || sortOrder == "")
sortOrder = TITLE;

Cursor c = sqlBuilder.query(
booksDB,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);

/*--me-register untuk mengamati URI bila ada perubahan--*/
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
Secara default, hasil dari query akan diurutkan menurut kolom 'title'. Query yang dihasilkan akan dikembalikan/dihasilkan dalam bentuk object 'Cursor'.

Supaya suatu buku baru bisa dimasukkan ke 'content provider', kita perlu me-override method 'insert()' :
@Override
public Uri insert(Uri uri, ContentValues values){
/* --menambahkan satu buku baru--*/
long rowID = booksDB.insert(DATABASE_TABLE, "", values);

/*-- bila berhasil ditambahkan--*/
if (rowID>0)
{
Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID);
getContext().getContentResolver().notifyChange(_uri, null);
return _uri;
}
throw new SQLException("Failed to insert row into " + uri);
}
Setelah baris/data baru berhasil dimasukkan, kita kemudian memanggil method 'notifyChange()' dari 'ContentResolver'. Ini akan memberi notifikasi (pada content provider) bahwa ada baris yang berubah.

Kemudian, untuk menghapus suatu buku, kita men-override method 'delete()' :
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
//arg0 = uri
// arg1 = selection
//arg2 = selectionArgs

int count = 0;
switch (uriMatcher.match(arg0)) {
case BOOKS:
count = booksDB.delete(DATABASE_TABLE, arg1, arg2);
break;
case BOOK_ID:
String id = arg0.getPathSegments().get(1);
count = booksDB.delete(DATABASE_TABLE, _ID +
" = " + id + (!TextUtils.isEmpty(arg1) ? " AND (" +
arg1 + ')' : ""), arg2);
break;
default:
throw new IllegalArgumentException("Unknown URI " + arg0);
}
getContext().getContentResolver().notifyChange(arg0, null);
return count;
}
Sama dengan sebelumnya, setelah menghapus suatu baris/data, method 'notifyChange()' dipanggil untuk memberitahu bahwa ada baris yang dihapus.

Terakhir, untuk update buku, kita men-override method 'update()' :
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
switch (uriMatcher.match(uri)) {
case BOOKS:
count = booksDB.update(DATABASE_TABLE, values, selection, selectionArgs);
break;
case BOOK_ID:
count = booksDB.update(DATABASE_TABLE, values,
_ID + " = " + uri.getPathSegments().get(1) +
(!TextUtils.isEmpty(selection) ? " AND (" +
selection + ')' : ""), selectionArgs);
break;
default: throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
Seperti halnya pada method 'insert()' dan 'delete()', kita memanggil method 'notifyChage()' dari 'ContentResolver' untuk memberitahu bahwa ada baris yang di-update.

Terakhir, kita perlu me-register 'content provider' kita ke Android, dengan cara menambahkan elemen <provider> ke file 'AndroidManifest.xml'.

No comments: