Content Provider - Sharing Data di Android

Di latihan-latihan sebelumnya kita sudah belajar tentang berbagai macam cara untuk menyimpan data: dengan menggunakan shared preferences, file, dan SQLite database. Menyimpan data di database dianjurkan apabila untuk menyimpan data yang kompleks dan terstruktur, dan tantangannya adalah sharing data karena database hanya bisa diakses oleh package yang membuatnya. 

Di latihan ini kita akan mempelajari cara Android berbagi data melalui penggunaan 'content provider'. Kita akan belajar tentang bagaimana menggunakan beberapa 'content provider' yang sudah 'built-in' maupun membuat 'content provider' sendiri untuk berbagi data lintas package.

Sharing Data di Android

Di Android, menggunakan 'content provider' adalah cara yang diajurkan dalam berbagi data lintas package. Anggap saja bahwa 'content provider' adalah seperti toko data. Bagaimana dia menyimpan data tidaklah penting bagi aplikasi yang menggunakannya; apa yang penting adalah bagaimana package yang lain bisa mengakses data yang disimpan di dalamnya dengan menggunakan interface pemrograman. 'Content provider' bertindak seperti layaknya database -- kita bisa men-query-nya, mengedit konten, maupun menambah dan menghapus kontennya. Tetapi, berbeda dengan database, karena 'content provider' menggunakan cara yang berbeda untuk menyimpan datanya. Datanya bisa disimpan di dalam suatu database, file, ata bahkan di dalam jaringan.

Android secara bawaan sudah memiliki banyak 'content provider' yang sudah tersedia dan bisa memberi manfaat, antara lain:
  • Browser - menyimpan data seperti bookmarks, history, dan seterusnya.
  • CallLog - menyimpan data seperti panggilan tak terjawab, detil-detil panggilan, dan seterusnya.
  • Contacts - menyimpan data detil-detil contact.
  • MediaSrore - menyimpan file-file media seperti audio, video, dan images.
  • Settings - menyimpan data berbagai macam setting dan preferences dari perangkat Android.
Selain berbagai 'content provider' yang sudah 'built-in', kita bisa juga membuat 'content provider' kita sendiri.

Untuk men-query suatu 'content provider', kita perlu menetapkan string untuk query dalam bentuk URI, dengan pilihan specifier opsional untuk baris tertentu. Format URI untuk query adalah seperti berikut:
<standard_prefix>://<authority>/<data_path>/<id>
Berikut penjelasan bagian-bagian dari URI di atas tersebut:
  • standard prefix untuk 'content provider' selalu content://.
  • authority menunjukkan nama dari 'content provider'. Contohnya contacts untuk 'content provider' Contacts yang sudah built-in. Untuk 'content provider' dari third-party, bisa merupakan nama yang valid dan lengkap, misalnya com.example.provider.
  • data path menunjukkan semacam data yang diminta. Contohnya, bila kita mendapatkan semua daftar contact dari 'content provider' Contact, maka data path tesebut adalah people dan URI nya akan seperti ini: content://contacts/people.
  • id menentukan data/baris tertentu yang diminta. Contohnya, bila kita mencari contact nomor 2 di 'content provider' Contacts, maka URI nya akan menjadi seperti ini: content://contacts/people/2
Beberapa contoh string untuk query:
  • content://media/internal/images - mengembalikan/menghasilkan semua images internal di perangkat.
  • content://media/external/images - mengembalikan/menghasilkan semua images di external storage (SD Card) di perangkat.
  • content://call_log/calls - mengembalikan/menghasilkan semua panggilan yang ada di Call Log.
  • content://browser/bookmarks - mengembalikan/menghasilkan semua bookmarks yang ada di browser.
Latihan menggunakan 'content provider'

Berikut adalah salah satu latihan menggunakan 'content provider' untuk memahami penggunaanya.

1. File layout xml: activity_main.xml
<?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:id="@+id/activity_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="com.example.provider.MainActivity"> 
<ListView
android:id="@+id/android:list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:stackFromBottom="false"
android:transcriptMode="normal"/> 
<TextView
android:id="@+id/contactID"
android:layout_height="wrap_content"
android:layout_width="wrap_content" /> 
<TextView
android:id="@+id/contactName"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/> 
</LinearLayout>

2. File java: MainActivity.java
package com.example.provider; 
import android.app.ListActivity;
import android.content.CursorLoader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.widget.CursorAdapter;
import android.widget.SimpleCursorAdapter;

public class MainActivity extends ListActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Uri allContacts = Uri.parse("content://contacts/people");

Cursor c;
if (Build.VERSION.SDK_INT < 11) {
/*sebelum honeycomb*/
c = managedQuery(allContacts, null, null, null, null);
} else {
/*Honeycomb dan seseudahnya*/
CursorLoader cursorLoader = new CursorLoader(this,allContacts,null,null,null,null);
c = cursorLoader.loadInBackground();
}

String[] columns = new String[] {
ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts._ID};

int[] views = new int[] {R.id.contactName, R.id.contactID};

SimpleCursorAdapter adapter;

if (Build.VERSION.SDK_INT < 11) {
/*sebelum honeycomb*/
adapter = new SimpleCursorAdapter(this, R.layout.activity_main, c, columns, views);
} else {
/*honeycom dan setelahnya*/
adapter = new SimpleCursorAdapter(this, R.layout.activity_main, c, columns, views,
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
}
this.setListAdapter(adapter);
}
}
3. File manifest: AndroidManifest.xml (tambahkan kode dengan huruf tebal)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.provider"> 
<uses-permission android:name="android.permission.READ_CONTACTS" /> 
<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>
</application> 
</manifest>

Kemudian jalankan emulator dan buatlah beberapa data contacts terlebih dahulu. Ketika menambahkan contacts dengan men-klik menu item New contact, akan muncul warning tentang mem-back up contacts kita. Kemudian klik tombol 'Keep Local' dan inputkan nama, nomor telphone, dan alamat email beberapa orang.

Kemudian jalankan lagi aplikasi di emulator Android. Berikut adalah tampilan emulator Android yang menampilkan daftar beberapa kontak yang baru kita buat.
Tampilan content provider
menampilkan daftar contacts

Penjelasan:

Dalam latihan ini kita mengambil semua daftar contacts yang disimpan dalam aplikasi Contacts dan menampilkanya dalam 'ListView'.

Pertama, kita menentukan URI untuk mengakses aplikasi Contacts:
Uri allContacts = Uri.parse("content://contacts/people");
Selanjutnya, kita perlu menerapkan check kondisi untuk mendeteksi versi perangkat dimana aplikasi akan berjalan:
Cursor c;
if (Build.VERSION.SDK_INT < 11) {
/*sebelum honeycomb*/
c = managedQuery(allContacts, null, null, null, null);
} else {
/*Honeycomb dan seseudahnya*/
CursorLoader cursorLoader = new CursorLoader(this,allContacts,null,null,null,null);
c = cursorLoader.loadInBackground();
}
Bila app ini berjalan pada perangkat sebelum Honeycomb (nilai dari variable android.os.Build.Version.SDK_INT di bawah 11), maka kita bisa menggunakan method 'managedQuery()' dari class 'Activity' untuk mendapatkan cursor. Suatu 'managed cursor' akan menangani dirinya sendiri ketika aplikasi sedang 'pause' dan membuat query ulang apabila aplikasi 'restart'. Kode-nya adalah seperti berikut:
Cursor c = managedQuery(allContacts, null, null, null, null);
kode tersebut sama/setara dengan:
Cursor c = getContentResolver().query(allContacts, null, null, null, null);
/*mengijinkan 'activity' untuk me-manage siklus hidup cursor berdasarkan siklus hidup 'activity' */
startManagingCursor(c);
Method 'getContentResolver() akan mengembalikan/menghasilkan object 'ContentResolver', yang akan membantu menangani URI konten dengan 'content provider' yang tepat.

Tetapi, mulai Android API level 11 (Honeycomb dan setelahnya), method 'managedQuery()' sudah di-deprecated/diperbarui (masih ada tetapi tidak dianjurkan untuk digunakan). Untuk perangkat Honeycomb dan setelahnya, kita menggunakan class 'CursorLoader':
CursorLoader cursorLoader = new CursorLoader(this,allContacts,null,null,null,null);
c = cursorLoader.loadInBackground();
Class 'CursorLoader' (hanya ada mulai Android API level 11 dan setelahnya) melakukan query cursor pada thread di background dan karena itu tidak men-block/menghalangi UI aplikasi.

Object 'SimpleCursorAdapter' akan me-meta-kan cursor ke 'TextViews' (atau 'ImageViews') yang ada file layout xml (activity_main.xml). Object ini me-meta-kan data (seperti yang disajikan oleh variabel 'columns' dalam kode di atas) ke 'views' (yang disajikan oleh variabel 'views'):
String[] columns = new String[] {
ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts._ID};

int[] views = new int[] {R.id.contactName, R.id.contactID};

SimpleCursorAdapter adapter;

if (Build.VERSION.SDK_INT < 11) {
/*sebelum honeycomb*/
adapter = new SimpleCursorAdapter(this, R.layout.activity_main, c, columns, views);
} else {
/*honeycom dan setelahnya*/
adapter = new SimpleCursorAdapter(this, R.layout.activity_main, c, columns, views,
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
this.setListAdapter(adapter);
Seperti halnya method 'managedQuery()', salah satu dari constructor class 'SimpleCursorAdapter' juga sudah di-deprecated/diperbarui. Untuk perangkat Honeycomb dan setelahnya, kita perlu menggunakan constructor baru untuk class 'SimpleCursorAdapter' dengan argument/parameter tambahan:
/*honeycom dan setelahnya*/
adapter = new SimpleCursorAdapter(this, R.layout.activity_main, c, columns, views,
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
Argumen/parameter tambahan tersebut (flag) akan memberi tahu adapter ketika ada perubahan pada 'content provider'.
Catatan:
Supaya app kita bisa mengakses app Contacts, kita perlu menambahkan permission READ_CONTACTS di dalam file AndroidMAnifest.xml.

No comments: