Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

Dasar-dasar penyedia materi

Penyedia materi mengelola akses ke repositori data pusat. Penyedia adalah bagian dari aplikasi Android, yang sering menyediakan UI-nya sendiri untuk menggunakan data. Akan tetapi, penyedia materi terutama dimaksudkan untuk digunakan oleh aplikasi lain, yang mengakses penyedia itu melalui objek klien penyedia. Bersama-sama, penyedia dan klien penyedia menawarkan antarmuka standar yang konsisten ke data yang juga menangani komunikasi antar-proses dan akses data yang aman.

Biasanya Anda bekerja dengan penyedia materi di salah satu dari dua skenario; Anda mungkin ingin mengimplementasikan kode untuk mengakses penyedia konten yang ada dalam aplikasi lain, atau Anda mungkin ingin membuat penyedia materi baru dalam aplikasi Anda untuk berbagi data dengan aplikasi lain. Topik ini membahas dasar-dasar bekerja dengan penyedia materi yang ada. Untuk mengetahui selengkapnya tentang mengimplementasikan penyedia materi dalam aplikasi Anda sendiri, lihat Membuat penyedia materi.

Topik ini menerangkan dasar-dasar dari hal-hal berikut:

  • Cara kerja penyedia materi.
  • API yang Anda gunakan untuk mengambil data dari penyedia materi.
  • API yang Anda gunakan untuk menyisipkan, memperbarui, atau menghapus data dalam penyedia materi.
  • Fitur API lainnya yang memudahkan kita menggunakan penyedia.

Ringkasan

Penyedia materi menyajikan data ke aplikasi eksternal sebagai satu atau beberapa tabel yang serupa dengan tabel-tabel yang ditemukan dalam database relasional. Sebuah baris mewakili instance beberapa tipe data yang dikumpulkan penyedia, dan tiap kolom dalam baris mewakili sepotong data yang dikumpulkan untuk sebuah instance.

Penyedia materi mengkoordinasikan akses ke layer penyimpanan data di aplikasi Anda untuk sejumlah API dan komponen yang berbeda seperti yang diilustrasikan pada gambar 1, antara lain:

  • Berbagi akses ke data aplikasi Anda dengan aplikasi lain
  • Mengirim data ke widget
  • Mengembalikan saran penelusuran kustom untuk aplikasi Anda melalui framework penelusuran menggunakan SearchRecentSuggestionsProvider
  • Menyinkronkan data aplikasi dengan server Anda menggunakan implementasi AbstractThreadedSyncAdapter
  • Memuat data di UI Anda menggunakan CursorLoader
Hubungan antara penyedia materi dan komponen lain.

Gambar 1. Hubungan antara penyedia materi dan komponen lain.

Mengakses penyedia

Bila Anda ingin mengakses data di penyedia materi, Anda menggunakan objek ContentResolver dalam Context aplikasi untuk berkomunikasi dengan penyedia sebagai klien. Objek ContentResolver berkomunikasi dengan objek penyedia, yakni instance class yang mengimplementasikan ContentProvider. Objek penyedia menerima permintaan data dari klien, melakukan aksi yang diminta, dan mengembalikan hasilnya. Objek ini memiliki metode yang memanggil metode dengan nama identik dalam objek penyedia, instance salah satu subclass konkret dari ContentProvider. Metode-metode ContentResolver menyediakan fungsi-fungsi dasar "CRUD" (create, retrieve, update, dan delete - buat, ambil, perbarui, dan hapus) pada penyimpanan yang persisten.

Pola umum untuk mengakses ContentProvider dari UI menggunakan CursorLoader untuk menjalankan kueri secara tidak bersamaan di latar belakang. Activity atau Fragment dalam UI Anda memanggil CursorLoader ke kueri, yang secara bergiliran mendapatkan ContentProvider menggunakan ContentResolver. Hal ini memungkinkan UI terus tersedia bagi pengguna saat kueri sedang berjalan. Pola ini melibatkan interaksi sejumlah objek yang berbeda, serta mekanisme penyimpanan dasarnya, seperti yang diilustrasikan pada gambar 2.

Interaksi antara ContentProvider, kelas lain, dan penyimpanan.

Gambar 2. Interaksi antara ContentProvider, kelas lain, dan penyimpanan.

Catatan: Untuk mengakses penyedia, aplikasi Anda biasanya harus meminta izin tertentu dalam file manifesnya. Pola pengembangan ini dijelaskan lebih detail di bagian Izin Penyedia Materi

Salah satu penyedia bawaan di platform Android adalah kamus pengguna, yang menyimpan ejaan kata-kata tidak-standar yang ingin disimpan pengguna. Tabel 1 mengilustrasikan wujud data yang mungkin ada dalam tabel penyedia ini:

Tabel 1: Contoh tabel kamus pengguna.

word app id frequency locale _ID
mapreduce user1 100 en_US 1
precompiler user14 200 fr_FR 2
applet user2 225 fr_CA 3
const user1 255 pt_BR 4
int user5 100 en_UK 5

Dalam tabel 1, tiap baris mewakili instance sebuah kata yang mungkin tidak ditemukan dalam kamus standar. Tiap kolom mewakili beberapa data untuk kata itu, misalnya bahasa lokal tempat kata itu ditemukan kali pertama. Header kolom adalah nama kolom yang disimpan dalam penyedia. Untuk mengacu ke bahasa lokal suatu baris, Anda mengacu ke kolom locale-nya. Untuk penyedia ini, kolom _ID berfungsi sebagai kolom "kunci utama" yang dipelihara oleh penyedia secara otomatis.

Untuk mendapatkan daftar kata dan bahasa lokalnya dari Penyedia Kamus Pengguna, Anda memanggil ContentResolver.query(). Metode query() memanggil metode ContentProvider.query() yang ditetapkan oleh Penyedia Kamus Pengguna. Baris-baris kode berikut menunjukkan sebuah panggilan ContentResolver.query():

Kotlin

// Queries the user dictionary and returns results
cursor = contentResolver.query(
        UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
        projection,                        // The columns to return for each row
        selectionClause,                   // Selection criteria
        selectionArgs.toTypedArray(),      // Selection criteria
        sortOrder                          // The sort order for the returned rows
)

Java

// Queries the user dictionary and returns results
cursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
    projection,                        // The columns to return for each row
    selectionClause,                   // Selection criteria
    selectionArgs,                     // Selection criteria
    sortOrder);                        // The sort order for the returned rows

Tabel 2 menampilkan bagaimana argumen ke query(Uri,projection,selection,selectionArgs,sortOrder) cocok dengan sebuah pernyataan SELECT di SQL:

Tabel 2: Query() dibandingkan dengan kueri SQL.

Argumen query() Kata kunci/parameter SELECT Catatan
Uri FROM table_name Uri memetakan ke tabel dalam penyedia yang bernama table_name.
projection col,col,col,... projection adalah satu larik kolom yang harus disertakan untuk tiap baris yang diambil.
selection WHERE col = value selection menetapkan kriteria untuk memilih baris.
selectionArgs (Tidak ada padanan persis. Argumen pemilihan mengganti placeholder ? dalam klausa pemilihan.)
sortOrder ORDER BY col,col,... sortOrder menetapkan urutan munculnya baris dalam Cursor yang dikembalikan.

URI Materi

URI materi adalah URI yang mengidentifikasi data dalam penyedia. URI Materi menyertakan nama simbolis seluruh penyedia (otoritasnya) dan sebuah nama yang menunjuk ke tabel (jalur). Bila Anda memanggil metode klien untuk mengakses tabel dalam penyedia, URI materi untuk tabel itu adalah salah satu argumennya.

Dalam baris kode sebelumnya, konstanta CONTENT_URI mengandung URI materi dari tabel "words" kamus pengguna. Objek ContentResolver akan mengurai otoritas URI, dan menggunakannya untuk "mengetahui" penyedia dengan membandingkan otoritas tersebut dengan sebuah tabel sistem berisi penyedia yang dikenal. ContentResolver kemudian bisa mengirim argumen kueri ke penyedia yang benar.

ContentProvider menggunakan bagian jalur dari URI materi untuk memilih tabel yang akan diakses. Penyedia biasanya memiliki jalur untuk tiap tabel yang dieksposnya.

Dalam baris kode sebelumnya, URI lengkap untuk tabel "words" adalah:

content://user_dictionary/words

ketika string user_dictionary adalah otoritas penyedia, dan string words adalah jalur tabel. String content:// (skema) selalu ada, dan mengidentifikasinya sebagai URI materi.

Banyak penyedia yang memungkinkan Anda mengakses satu baris dalam tabel dengan menambahkan sebuah nilai ID ke akhir URI. Misalnya, untuk mengambil sebuah baris yang _ID-nya adalah 4 dari kamus pengguna, Anda bisa menggunakan URI materi ini:

Kotlin

val singleUri: Uri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI, 4)

Java

Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

Anda akan sering menggunakan nilai-nilai ID bila telah mengambil satu set baris kemudian ingin memperbarui atau menghapus salah satunya.

Catatan: Class Uri dan Uri.Builder berisi metode praktis untuk membangun objek URI yang tersusun dengan baik dari string. Class ContentUris berisi metode praktis untuk menambahkan nilai id ke URI. Cuplikan kode sebelumnya menggunakan withAppendedId() untuk menambahkan id ke URI materi UserDictionary.

Mengambil data dari penyedia

Bagian ini menerangkan cara mengambil data dari penyedia, dengan menggunakan Penyedia Kamus Pengguna sebagai contoh.

Demi kejelasan, cuplikan kode di bagian ini memanggil ContentResolver.query() pada "UI thread". Akan tetapi, dalam kode sesungguhnya, Anda harus melakukan kueri secara tidak bersamaan pada sebuah thread terpisah. Satu cara melakukannya adalah menggunakan class CursorLoader, yang dijelaskan lebih detail dalam panduan Loader. Juga, baris-baris kode tersebut hanyalah cuplikan; tidak menunjukkan sebuah aplikasi lengkap.

Untuk mengambil data dari penyedia, ikutilah langkah-langkah dasar ini:

  1. Minta izin akses baca untuk penyedia itu.
  2. Definisikan kode yang mengirim kueri ke penyedia.

Meminta izin akses baca

Untuk mengambil data dari penyedia, aplikasi Anda memerlukan "izin akses baca" untuk penyedia itu. Anda tidak bisa meminta izin ini saat waktu proses; sebagai gantinya, Anda harus menetapkan bahwa Anda memerlukan izin ini dalam manifes, dengan menggunakan elemen <uses-permission> dan nama persis izin yang ditetapkan oleh penyedia itu. Bila menetapkan elemen ini dalam manifes, Anda secara efektif "meminta" izin ini untuk aplikasi Anda. Bila pengguna menginstal aplikasi Anda, mereka secara implisit akan memberikan permintaan ini.

Untuk menemukan nama persis dari izin akses baca untuk penyedia yang sedang Anda gunakan, serta nama-nama izin akses lain yang digunakan oleh penyedia, lihatlah dalam dokumentasi penyedia.

Peran izin dalam yang mengakses penyedia dijelaskan lebih detail di bagian Izin penyedia materi.

Penyedia Kamus Pengguna menetapkan izin android.permission.READ_USER_DICTIONARY dalam file manifesnya, sehingga aplikasi yang ingin membaca dari penyedia itu harus meminta izin ini.

Membuat kueri

Langkah berikutnya dalam mengambil data dari penyedia adalah melakukan kueri. Cuplikan kode pertama ini menetapkan beberapa variabel untuk mengakses Penyedia Kamus Pengguna:

Kotlin

// A "projection" defines the columns that will be returned for each row
private val mProjection: Array<String> = arrayOf(
        UserDictionary.Words._ID,    // Contract class constant for the _ID column name
        UserDictionary.Words.WORD,   // Contract class constant for the word column name
        UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
)

// Defines a string to contain the selection clause
private var selectionClause: String? = null

// Declares an array to contain selection arguments
private lateinit var selectionArgs: Array<String>

Java

// A "projection" defines the columns that will be returned for each row
String[] mProjection =
{
    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
    UserDictionary.Words.WORD,   // Contract class constant for the word column name
    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
};

// Defines a string to contain the selection clause
String selectionClause = null;

// Initializes an array to contain selection arguments
String[] selectionArgs = {""};

Cuplikan berikutnya menampilkan cara menggunakan ContentResolver.query(), dengan menggunakan Penyedia Kamus Pengguna sebagai contoh. Kueri klien penyedia serupa dengan kueri SQL, dan berisi satu set kolom yang akan dikembalikan, satu set kriteria pemilihan, dan urutan sortir.

Set kolom yang harus dikembalikan kueri disebut dengan proyeksi (variabel mProjection).

Ekspresi yang menetapkan baris yang harus diambil dipecah menjadi klausa pemilihan dan argumen pemilihan. Klausa pemilihan adalah kombinasi ekspresi logis dan Boolean, nama kolom, dan nilai (variabel mSelectionClause). Jika Anda menetapkan parameter ? yang bisa diganti, bukannya nilai, metode kueri akan mengambil nilai dari larik argumen pemilihan (variabel mSelectionArgs).

Dalam cuplikan berikutnya, jika pengguna tidak memasukkan sebuah kata, klausa pemilihan akan disetel ke null, dan kueri mengembalikan semua kata dalam penyedia. Jika pengguna memasukkan sebuah kata, klausa pemilihan akan disetel ke UserDictionary.Words.WORD + " = ?" dan elemen pertama larik argumen pemilihan disetel ke kata yang dimasukkan pengguna.

Kotlin

/*
 * This declares String array to contain the selection arguments.
 */
private lateinit var selectionArgs: Array<String>

// Gets a word from the UI
searchString = searchWord.text.toString()

// Remember to insert code here to check for invalid or malicious input.

// If the word is the empty string, gets everything
selectionArgs = searchString?.takeIf { it.isNotEmpty() }?.let {
    selectionClause = "${UserDictionary.Words.WORD} = ?"
    arrayOf(it)
} ?: run {
    selectionClause = null
    emptyArray<String>()
}

// Does a query against the table and returns a Cursor object
mCursor = contentResolver.query(
        UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
        projection,                       // The columns to return for each row
        selectionClause,                  // Either null, or the word the user entered
        selectionArgs,                    // Either empty, or the string the user entered
        sortOrder                         // The sort order for the returned rows
)

// Some providers return null if an error occurs, others throw an exception
when (mCursor?.count) {
    null -> {
        /*
         * Insert code here to handle the error. Be sure not to use the cursor!
         * You may want to call android.util.Log.e() to log this error.
         *
         */
    }
    0 -> {
        /*
         * Insert code here to notify the user that the search was unsuccessful. This isn't
         * necessarily an error. You may want to offer the user the option to insert a new
         * row, or re-type the search term.
         */
    }
    else -> {
        // Insert code here to do something with the results
    }
}

Java

/*
 * This defines a one-element String array to contain the selection argument.
 */
String[] selectionArgs = {""};

// Gets a word from the UI
searchString = searchWord.getText().toString();

// Remember to insert code here to check for invalid or malicious input.

// If the word is the empty string, gets everything
if (TextUtils.isEmpty(searchString)) {
    // Setting the selection clause to null will return all words
    selectionClause = null;
    selectionArgs[0] = "";

} else {
    // Constructs a selection clause that matches the word that the user entered.
    selectionClause = UserDictionary.Words.WORD + " = ?";

    // Moves the user's input string to the selection arguments.
    selectionArgs[0] = searchString;

}

// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
    projection,                       // The columns to return for each row
    selectionClause,                  // Either null, or the word the user entered
    selectionArgs,                    // Either empty, or the string the user entered
    sortOrder);                       // The sort order for the returned rows

// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
    /*
     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
     * call android.util.Log.e() to log this error.
     *
     */
// If the Cursor is empty, the provider found no matches
} else if (mCursor.getCount() < 1) {

    /*
     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
     * an error. You may want to offer the user the option to insert a new row, or re-type the
     * search term.
     */

} else {
    // Insert code here to do something with the results

}

Kueri ini merupakan analog dengan pernyataan SQL:

SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;

Dalam pernyataan SQL ini, nama kolom yang sesungguhnya digunakan sebagai ganti konstanta kelas kontrak.

Melindungi dari masukan merusak

Jika data dikelola oleh penyedia materi berada dalam database SQL, memasukkan data tak dipercaya eksternal ke dalam pernyataan SQL mentah bisa menyebabkan injeksi SQL.

Perhatikan klausa pemilihan ini:

Kotlin

// Constructs a selection clause by concatenating the user's input to the column name
var selectionClause = "var = $mUserInput"

Java

// Constructs a selection clause by concatenating the user's input to the column name
String selectionClause = "var = " + userInput;

Jika melakukannya, Anda akan membuat pengguna menyambungkan SQL merusak ke pernyataan SQL Anda. Misalnya, pengguna bisa memasukkan "nothing; DROP TABLE *;" untuk mUserInput, yang akan menghasilkan klausa pemilihan var = nothing; DROP TABLE *;. Karena klausa pemilihan diperlakukan sebagai pernyataan SQL, hal ini bisa menyebabkan penyedia itu menghapus semua tabel dalam database SQLite yang mendasarinya (kecuali penyedia disiapkan untuk menangkap upaya injeksi SQL).

Untuk menghindari masalah ini, gunakan klausa pemilihan yang menggunakan ? sebagai parameter yang bisa diganti dan larik argumen pemilihan yang terpisah. Bila Anda melakukannya, masukan pengguna akan dibatasi secara langsung pada kueri agar tidak ditafsirkan sebagai bagian dari pernyataan SQL. Karena tidak diperlakukan sebagai SQL, masukan pengguna tidak bisa menyuntikkan SQL merusak. Sebagai ganti menggunakan penyambungan untuk menyertakan masukan pengguna, gunakan klausa pemilihan ini:

Kotlin

// Constructs a selection clause with a replaceable parameter
var selectionClause = "var = ?"

Java

// Constructs a selection clause with a replaceable parameter
String selectionClause =  "var = ?";

Buat larik argumen pemilihan seperti ini:

Kotlin

// Defines a mutable list to contain the selection arguments
var selectionArgs: MutableList<String> = mutableListOf()

Java

// Defines an array to contain the selection arguments
String[] selectionArgs = {""};

Masukkan nilai dalam larik argumen pemilihan seperti ini:

Kotlin

// Adds the user's input to the selection argument
selectionArgs += userInput

Java

// Sets the selection argument to the user's input
selectionArgs[0] = userInput;

Sebuah klausa pemilihan yang menggunakan ? sebagai parameter yang bisa diganti dan sebuah larik argumen pemilihan adalah cara yang lebih disukai untuk menyebutkan pemilihan, sekalipun penyedia tidak dibuat berdasarkan database SQL.

Menampilkan hasil kueri

Metode klien ContentResolver.query() selalu mengembalikan Cursor berisi kolom-kolom yang ditetapkan oleh proyeksi kueri untuk baris yang cocok dengan kriteria pemilihan kueri. Objek Cursor menyediakan akses baca acak ke baris dan kolom yang dimuatnya. Dengan metode Cursor, Anda bisa mengulang baris-baris dalam hasil, menentukan tipe data tiap kolom, mengambil data dari kolom, dan memeriksa properti lain dari hasil. Beberapa implementasi Cursor akan memperbarui objek secara otomatis bila data penyedia berubah, atau memicu metode dalam objek pengamat bila Cursor berubah, atau keduanya.

Catatan: Penyedia bisa membatasi akses ke kolom berdasarkan sifat objek yang melakukan kueri. Misalnya, Penyedia Kontak membatasi akses untuk beberapa kolom pada adaptor sinkronisasi, sehingga tidak akan mengembalikannya ke aktivitas atau layanan.

Jika tidak ada baris yang cocok dengan kriteria pemilihan, penyedia akan mengembalikan objek Cursor dengan Cursor.getCount() adalah 0 (kursor kosong).

Jika terjadi kesalahan internal, hasil kueri akan bergantung pada penyedia tertentu. Penyedia bisa memilih untuk mengembalikan null, atau melontarkan Exception.

Karena Cursor adalah "daftar" baris, cara yang cocok untuk menampilkan materi Cursor adalah menautkannya dengan ListView melalui SimpleCursorAdapter.

Cuplikan berikut melanjutkan kode dari cuplikan sebelumnya. Cuplikan ini membuat objek SimpleCursorAdapter berisi Cursor yang diambil oleh kueri, dan mengatur objek ini menjadi adaptor bagi ListView:

Kotlin

// Defines a list of columns to retrieve from the Cursor and load into an output row
val wordListColumns : Array<String> = arrayOf(
        UserDictionary.Words.WORD,      // Contract class constant containing the word column name
        UserDictionary.Words.LOCALE     // Contract class constant containing the locale column name
)

// Defines a list of View IDs that will receive the Cursor columns for each row
val wordListItems = intArrayOf(R.id.dictWord, R.id.locale)

// Creates a new SimpleCursorAdapter
cursorAdapter = SimpleCursorAdapter(
        applicationContext,             // The application's Context object
        R.layout.wordlistrow,           // A layout in XML for one row in the ListView
        mCursor,                        // The result from the query
        wordListColumns,               // A string array of column names in the cursor
        wordListItems,                 // An integer array of view IDs in the row layout
        0                               // Flags (usually none are needed)
)

// Sets the adapter for the ListView
wordList.setAdapter(cursorAdapter)

Java

// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] wordListColumns =
{
    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
};

// Defines a list of View IDs that will receive the Cursor columns for each row
int[] wordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
cursorAdapter = new SimpleCursorAdapter(
    getApplicationContext(),               // The application's Context object
    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
    mCursor,                               // The result from the query
    wordListColumns,                      // A string array of column names in the cursor
    wordListItems,                        // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)

// Sets the adapter for the ListView
wordList.setAdapter(cursorAdapter);

Catatan: Untuk mendukung ListView dengan Cursor, kursor harus berisi kolom bernama _ID. Karena itu, kueri yang ditampilkan sebelumnya mengambil kolom _ID untuk tabel "words", walaupun ListView tidak menampilkannya. Pembatasan ini juga menjelaskan mengapa sebagian besar penyedia memiliki kolom _ID untuk masing-masing tabelnya.

Mendapatkan data dari hasil kueri

Daripada sekadar menampilkan hasil kueri, Anda bisa menggunakannya untuk tugas-tugas lain. Misalnya, Anda bisa mengambil ejaan dari kamus pengguna kemudian mencarinya dalam penyedia lain. Caranya, ulangi baris-baris dalam Cursor:

Kotlin

/*
* Only executes if the cursor is valid. The User Dictionary Provider returns null if
* an internal error occurs. Other providers may throw an Exception instead of returning null.
*/
mCursor?.apply {
    // Determine the column index of the column named "word"
    val index: Int = getColumnIndex(UserDictionary.Words.WORD)

    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
     * exception.
     */
    while (moveToNext()) {
        // Gets the value from the column.
        newWord = getString(index)

        // Insert code here to process the retrieved word.

        ...

        // end of while loop
    }
}

Java

// Determine the column index of the column named "word"
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);

/*
 * Only executes if the cursor is valid. The User Dictionary Provider returns null if
 * an internal error occurs. Other providers may throw an Exception instead of returning null.
 */

if (mCursor != null) {
    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
     * exception.
     */
    while (mCursor.moveToNext()) {

        // Gets the value from the column.
        newWord = mCursor.getString(index);

        // Insert code here to process the retrieved word.

        ...

        // end of while loop
    }
} else {

    // Insert code here to report an error if the cursor is null or the provider threw an exception.
}

Implementasi Cursor berisi beberapa metode "get" untuk mengambil berbagai tipe data dari objek. Misalnya, cuplikan sebelumnya menggunakan getString(). Implementasi juga memiliki metode getType() yang mengembalikan nilai yang menunjukkan tipe data kolom.

Izin penyedia materi

Aplikasi penyedia bisa menetapkan izin yang harus dimiliki aplikasi lain untuk mengakses data penyedia. Izin ini akan memastikan bahwa pengguna mengetahui data yang coba diakses oleh aplikasi. Berdasarkan ketentuan penyedia, aplikasi lain meminta izin yang diperlukannya untuk mengakses penyedia. Pengguna akhir akan melihat izin yang diminta saat menginstal aplikasi.

Jika aplikasi penyedia tidak menetapkan izin apa pun, maka aplikasi lain tidak memiliki akses ke data penyedia. Akan tetapi, komponen-komponen dalam aplikasi penyedia selalu memiliki akses penuh untuk baca dan tulis, izin apa pun yang ditetapkan.

Seperti disebutkan sebelumnya, Penyedia Kamus Pengguna mensyaratkan izin android.permission.READ_USER_DICTIONARY untuk mengambil data darinya. Penyedia memiliki izin android.permission.WRITE_USER_DICTIONARY yang terpisah untuk menyisipkan, memperbarui, atau menghapus data.

Untuk mendapatkan izin yang diperlukan untuk mengakses penyedia, aplikasi memintanya dengan elemen <uses-permission> dalam file manifesnya. Bila Android Package Manager menginstal aplikasi, pengguna harus menyetujui semua izin yang diminta aplikasi. Jika pengguna menyetujui semuanya, Package Manager akan melanjutkan pemasangan; jika pengguna tidak menyetujuinya, Package Manager membatalkan penginstalan.

Elemen <uses-permission> berikut meminta akses baca ke Penyedia Kamus Pengguna:

<uses-permission android:name="android.permission.READ_USER_DICTIONARY">

Dampak izin pada akses penyedia dijelaskan secara lebih detail dalam panduan Keamanan dan izin.

Menyisipkan, memperbarui, dan menghapus data

Lewat cara yang sama dengan cara mengambil data dari penyedia, Anda juga menggunakan interaksi antara klien penyedia dan ContentProvider penyedia untuk memodifikasi data. Anda memanggil metode ContentResolver dengan argumen yang diteruskan ke metode ContentProvider yang sesuai. Penyedia dan klien penyedia menangani secara otomatis keamanan dan komunikasi antar-proses.

Menyisipkan data

Untuk menyisipkan data ke penyedia, Anda memanggil metode ContentResolver.insert(). Metode ini menyisipkan sebuah baris baru ke penyedia itu dan mengembalikan URI materi untuk baris itu. Cuplikan ini menampilkan cara menyisipkan sebuah kata baru ke Penyedia Kamus Pengguna:

Kotlin

// Defines a new Uri object that receives the result of the insertion
lateinit var newUri: Uri

...

// Defines an object to contain the new values to insert
val newValues = ContentValues().apply {
    /*
     * Sets the values of each column and inserts the word. The arguments to the "put"
     * method are "column name" and "value"
     */
    put(UserDictionary.Words.APP_ID, "example.user")
    put(UserDictionary.Words.LOCALE, "en_US")
    put(UserDictionary.Words.WORD, "insert")
    put(UserDictionary.Words.FREQUENCY, "100")

}

newUri = contentResolver.insert(
        UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
        newValues                          // the values to insert
)

Java

// Defines a new Uri object that receives the result of the insertion
Uri newUri;

...

// Defines an object to contain the new values to insert
ContentValues newValues = new ContentValues();

/*
 * Sets the values of each column and inserts the word. The arguments to the "put"
 * method are "column name" and "value"
 */
newValues.put(UserDictionary.Words.APP_ID, "example.user");
newValues.put(UserDictionary.Words.LOCALE, "en_US");
newValues.put(UserDictionary.Words.WORD, "insert");
newValues.put(UserDictionary.Words.FREQUENCY, "100");

newUri = getContentResolver().insert(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    newValues                          // the values to insert
);

Data untuk baris baru masuk ke dalam satu objek ContentValues, yang serupa bentuknya dengan kursor satu-baris. Kolom dalam objek ini tidak perlu memiliki tipe data yang sama, dan jika Anda tidak ingin menetapkan nilai sama sekali, Anda bisa menyetel kolom ke null dengan menggunakan ContentValues.putNull().

Cuplikan ini tidak menambahkan kolom _ID, karena kolom ini dipelihara secara otomatis. Penyedia menetapkan sebuah nilai unik _ID ke setiap baris yang ditambahkan. Penyedia biasanya menggunakan nilai ini sebagai kunci utama tabel.

URI materi yang dikembalikan dalam newUri akan mengidentifikasi baris yang baru ditambahkan, dengan format berikut:

content://user_dictionary/words/<id_value>

<id_value> adalah materi _ID untuk baris baru. Kebanyakan penyedia bisa mendeteksi bentuk URI materi ini secara otomatis kemudian melakukan operasi yang diminta pada baris tersebut.

Untuk mendapatkan nilai _ID dari Uri yang dikembalikan, panggil ContentUris.parseId().

Memperbarui data

Untuk memperbarui sebuah baris, gunakan objek ContentValues dengan nilai-nilai yang diperbarui, persis seperti yang Anda lakukan pada penyisipan, dan kriteria pemilihan persis seperti yang Anda lakukan pada kueri. Metode klien yang Anda gunakan adalah ContentResolver.update(). Anda hanya perlu menambahkan nilai-nilai ke objek ContentValues untuk kolom yang sedang Anda perbarui. Jika Anda ingin mengosongkan isi kolom, setel nilai ke null.

Cuplikan berikut mengubah semua baris yang kolom lokalnya memiliki bahasa "en" ke lokal null. Nilai hasil adalah jumlah baris yang diperbarui:

Kotlin

// Defines an object to contain the updated values
val updateValues = ContentValues().apply {
    /*
     * Sets the updated value and updates the selected words.
     */
    putNull(UserDictionary.Words.LOCALE)
}

// Defines selection criteria for the rows you want to update
val selectionClause: String = UserDictionary.Words.LOCALE + "LIKE ?"
val selectionArgs: Array<String> = arrayOf("en_%")

// Defines a variable to contain the number of updated rows
var rowsUpdated: Int = 0

...

rowsUpdated = contentResolver.update(
        UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
        updateValues,                      // the columns to update
        selectionClause,                   // the column to select on
        selectionArgs                      // the value to compare to
)

Java

// Defines an object to contain the updated values
ContentValues updateValues = new ContentValues();

// Defines selection criteria for the rows you want to update
String selectionClause = UserDictionary.Words.LOCALE +  " LIKE ?";
String[] selectionArgs = {"en_%"};

// Defines a variable to contain the number of updated rows
int rowsUpdated = 0;

...

/*
 * Sets the updated value and updates the selected words.
 */
updateValues.putNull(UserDictionary.Words.LOCALE);

rowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    updateValues,                      // the columns to update
    selectionClause,                   // the column to select on
    selectionArgs                      // the value to compare to
);

Anda juga harus membersihkan masukan pengguna bila memanggil ContentResolver.update(). Untuk mengetahui selengkapnya tentang hal ini, bacalah bagian Melindungi dari masukan merusak.

Menghapus data

Menghapus baris serupa dengan mengambil baris data: Anda menetapkan kriteria pemilihan untuk baris yang ingin Anda hapus dan metode klien akan mengembalikan jumlah baris yang dihapus. Cuplikan berikut menghapus baris yang appid-nya sama dengan "user". Metode mengembalikan jumlah baris yang dihapus.

Kotlin

// Defines selection criteria for the rows you want to delete
val selectionClause = "${UserDictionary.Words.LOCALE} LIKE ?"
val selectionArgs: Array<String> = arrayOf("user")

// Defines a variable to contain the number of rows deleted
var rowsDeleted: Int = 0

...

// Deletes the words that match the selection criteria
rowsDeleted = contentResolver.delete(
        UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
        selectionClause,                   // the column to select on
        selectionArgs                      // the value to compare to
)

Java

// Defines selection criteria for the rows you want to delete
String selectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] selectionArgs = {"user"};

// Defines a variable to contain the number of rows deleted
int rowsDeleted = 0;

...

// Deletes the words that match the selection criteria
rowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    selectionClause,                   // the column to select on
    selectionArgs                      // the value to compare to
);

Anda juga harus membersihkan masukan pengguna bila memanggil ContentResolver.delete(). Untuk mengetahui selengkapnya tentang hal ini, bacalah bagian Melindungi dari masukan merusak.

Tipe Data Penyedia

Penyedia materi bisa menawarkan berbagai tipe data. Penyedia Kamus Pengguna hanya menawarkan teks, namun penyedia juga bisa menawarkan format berikut:

  • integer
  • long integer (long)
  • floating point
  • long floating point (double)

Tipe data lain yang sering digunakan penyedia adalah Binary Large OBject (BLOB) yang diimplementasikan sebagai larik byte 64 KB. Anda bisa melihat tipe data yang tersedia dengan memperhatikan metode "get" class Cursor.

Tipe data tiap kolom dalam penyedia biasanya tercantum dalam dokumentasinya. Tipe data untuk Penyedia Kamus Pengguna tercantum dalam dokumentasi acuan untuk kelas kontraknya UserDictionary.Words (kelas kontrak dijelaskan di bagian Kelas-kelas Kontrak). Anda juga bisa menentukan tipe data dengan memanggil Cursor.getType().

Penyedia juga memelihara informasi tipe data MIME untuk tiap URI materi yang didefinisikannya. Anda bisa menggunakan informasi tipe MIME untuk mengetahui apakah aplikasi Anda bisa menangani data yang disediakan penyedia, atau memilih tipe penanganan berdasarkan tipe MIME. Anda biasanya memerlukan tipe MIME saat menggunakan penyedia yang berisi struktur atau file data yang kompleks. Misalnya, tabel ContactsContract.Data dalam Penyedia Kontak menggunakan tipe MIME untuk memberi label tipe data kontak yang disimpan di tiap baris. Untuk mendapatkan tipe MIME yang sesuai dengan URI materi, panggil ContentResolver.getType().

Bagian Acuan tipe MIME menerangkan sintaks tipe MIME baik yang standar maupun khusus.

Bentuk-bentuk alternatif akses penyedia

Tiga bentuk alternatif akses penyedia adalah penting dalam development aplikasi:

  • Akses batch: Anda bisa membuat sebuah batch panggilan akses dengan metode-metode dalam class ContentProviderOperation, kemudian menerapkannya dengan ContentResolver.applyBatch().
  • Kueri asinkron: Anda harus melakukan kueri dalam thread terpisah. Satu cara melakukannya adalah menggunakan objek CursorLoader. Contoh-contoh dalam panduan Loader memperagakan cara melakukannya.
  • Akses data melalui maksud: Walaupun tidak bisa mengirim intent ke penyedia secara langsung, Anda bisa mengirim intent ke aplikasi penyedia, yang biasanya paling lengkap dibekali untuk memodifikasi data penyedia.

Akses batch dan modifikasi melalui intent dijelaskan dalam bagian-bagian berikut.

Akses batch

Akses batch ke penyedia berguna untuk menyisipkan baris dalam jumlah besar, atau menyisipkan baris ke dalam beberapa tabel dalam panggilan metode yang sama, atau biasanya melakukan satu set operasi lintas batas proses sebagai transaksi (operasi atomik).

Untuk mengakses penyedia dalam "mode batch", buat satu larik objek ContentProviderOperation, kemudian kirim larik itu ke penyedia materi dengan ContentResolver.applyBatch(). Anda meneruskan otoritas penyedia materi ke metode ini, daripada URI materi tertentu. Ini memungkinkan tiap objek ContentProviderOperation dalam larik untuk bekerja terhadap tabel yang berbeda. Panggilan ke ContentResolver.applyBatch() mengembalikan satu larik hasil.

Keterangan kelas kontrak ContactsContract.RawContacts menyertakan cuplikan kode yang memperagakan penyisipan batch. Contoh aplikasi Contacts Manager berisi contoh akses batch dalam file sumber ContactAdder.java-nya.

Akses data melalui intent

Intent bisa menyediakan akses tidak langsung ke penyedia materi. Anda memperbolehkan pengguna mengakses data dalam penyedia sekalipun aplikasi Anda tidak memiliki izin akses, baik dengan mendapatkan intent yang dihasilkan aplikasi yang memiliki izin, atau dengan mengaktifkan aplikasi yang memiliki izin dan membiarkan pengguna melakukan pekerjaan di dalamnya.

Mendapatkan akses dengan izin sementara

Anda bisa mengakses data dalam penyedia materi, sekalipun tidak memiliki izin akses yang sesuai, dengan mengirimkan intent ke aplikasi yang memang memiliki izin dan menerima hasil berupa intent berisi izin "URI". Inilah izin untuk URI materi tertentu yang berlaku hingga aktivitas yang menerima izin selesai. Aplikasi yang memiliki izin tetap akan memberikan izin sementara dengan menyetel flag dalam intent yang dihasilkan:

Catatan: Flag ini tidak memberikan akses baca atau tulis umum ke penyedia yang otoritasnya dimuat dalam URI materi. Aksesnya hanya untuk URI itu sendiri.

Penyedia mendefinisikan izin URI untuk URI materi dalam manifesnya, dengan menggunakan atribut android:grantUriPermission dari elemen <provider>, serta elemen anak <grant-uri-permission> dari elemen <provider>. Mekanisme izin URI dijelaskan lebih detail dalam panduan Ringkasan izin.

Misalnya, Anda bisa mengambil data untuk satu kontak di Penyedia Kontak, sekalipun tidak memiliki izin READ_CONTACTS. Anda mungkin ingin melakukan ini dalam aplikasi yang mengirim kartu ucapan elektronik ke seorang kenalan pada hari ulang tahunnya. Sebagai ganti meminta READ_CONTACTS, yang memberi Anda akses ke semua kontak pengguna dan semua informasinya, Anda lebih baik membiarkan pengguna mengontrol kontak-kontak yang akan digunakan oleh aplikasi Anda. Caranya, gunakan proses berikut:

  1. Aplikasi Anda akan mengirim intent berisi aksi ACTION_PICK dan tipe MIME "contacts" CONTENT_ITEM_TYPE, dengan menggunakan metode startActivityForResult().
  2. Karena intent ini cocok dengan filter intent untuk aktivitas "pemilihan" Aplikasi Orang, aktivitas akan muncul ke latar depan.
  3. Dalam aktivitas pemilihan, pengguna memilih sebuah kontak untuk diperbarui. Bila ini terjadi, aktivitas pemilihan akan memanggil setResult(resultcode, intent) untuk membuat intent yang akan diberikan kembali ke aplikasi Anda. Intent berisi URI materi kontak yang dipilih pengguna, dan flag "tambahan" FLAG_GRANT_READ_URI_PERMISSION. Semua flag ini memberikan izin URI ke aplikasi Anda untuk membaca data kontak yang ditunjuk oleh URI materi. Aktivitas pemilihan kemudian memanggil finish() untuk mengembalikan kontrol ke aplikasi Anda.
  4. Aktivitas Anda akan kembali ke latar depan, dan sistem akan memanggil metode onActivityResult() aktivitas Anda. Metode ini menerima intent yang dihasilkan oleh aktivitas pemilihan dalam Aplikasi Orang.
  5. Dengan URI materi dari intent yang dihasilkan, Anda bisa membaca data kontak dari Penyedia Kontak, sekalipun Anda tidak meminta izin akses baca tetap ke penyedia dalam manifes Anda. Anda kemudian bisa mendapatkan informasi hari ulang tahun si kontak atau alamat emailnya, kemudian mengirim kartu ucapan elektronik.

Menggunakan aplikasi lain

Satu cara mudah untuk memungkinkan pengguna memodifikasi data yang izin aksesnya tidak Anda miliki adalah mengaktifkan aplikasi yang memiliki izin dan membiarkan pengguna melakukan pekerjaannya di sana.

Misalnya, aplikasi Kalender menerima intent ACTION_INSERT, yang memungkinkan Anda mengaktifkan UI penyisipan aplikasi itu. Anda bisa meneruskan data "tambahan" dalam intent ini, yang digunakan aplikasi untuk mengisi dahulu UI-nya. Karena kejadian berulang memiliki sintaks yang rumit, cara yang lebih disukai untuk menyisipkan kejadian ke dalam Penyedia Kalender adalah mengaktifkan aplikasi Kalender dengan ACTION_INSERT kemudian membiarkan pengguna menyisipkan kejadian di sana.

Menampilkan data dengan aplikasi pembantu

Jika aplikasi Anda memang memiliki izin akses, Anda masih mungkin perlu menggunakan intent untuk menampilkan data dalam aplikasi lain. Misalnya, aplikasi Kalender menerima intent ACTION_VIEW, yang menampilkan tanggal atau kejadian tertentu. Hal ini memungkinkan Anda menampilkan informasi kalender tanpa harus membuat UI sendiri. Untuk mengetahui selengkapnya tentang fitur ini, lihat panduan Penyedia Kalender.

Aplikasi yang Anda kirimi intent tidak harus aplikasi yang terkait dengan penyedia. Misalnya, Anda bisa mengambil satu kontak dari Penyedia Kontak, kemudian mengirim intent ACTION_VIEW berisi URI materi untuk gambar kontak itu ke penampil gambar.

Kelas-kelas Kontrak

Kelas kontrak mendefinisikan konstanta yang membantu aplikasi menggunakan URI materi, nama kolom, tindakan intent, dan fitur lain pada penyedia materi. Class kontrak tidak disertakan secara otomatis bersama penyedia; developer penyedia harus menetapkannya kemudian membuatnya tersedia bagi developer lain. Banyak penyedia yang disertakan pada platform Android memiliki kelas kontrak yang sesuai dalam android.provider paketnya.

Misalnya, Penyedia Kamus Pengguna memiliki kelas kontrak UserDictionary yang berisi URI materi dan konstanta nama kolom. URI materi untuk tabel "words" ditetapkan dalam konstanta UserDictionary.Words.CONTENT_URI. Kelas UserDictionary.Words juga berisi konstanta nama kolom, yang digunakan dalam cuplikan contoh pada panduan ini. Misalnya, sebuah proyeksi kueri bisa ditetapkan sebagai:

Kotlin

val projection : Array<String> = arrayOf(
        UserDictionary.Words._ID,
        UserDictionary.Words.WORD,
        UserDictionary.Words.LOCALE
)

Java

String[] projection =
{
    UserDictionary.Words._ID,
    UserDictionary.Words.WORD,
    UserDictionary.Words.LOCALE
};

Kelas kontrak lain adalah ContactsContract untuk Penyedia Kontak. Dokumentasi acuan untuk class ini menyertakan contoh cuplikan kode. Salah satu subkelasnya, ContactsContract.Intents.Insert, adalah class kontrak yang berisi konstanta untuk intent dan data intent.

Acuan Tipe MIME

Penyedia materi bisa mengembalikan tipe media MIME standar, atau string tipe MIME khusus, atau keduanya.

Tipe MIME memiliki format

type/subtype

Misalnya, tipe MIME text/html yang dikenal luas memiliki tipe text dan subtipe html. Jika penyedia mengembalikan tipe ini untuk sebuah URI, artinya kueri dengan URI itu akan mengembalikan teks berisi tag HTML.

String tipe MIME khusus, yang juga disebut dengan tipe MIME "khusus-vendor", memiliki nilai-nilai tipe dan subtipe yang lebih kompleks. Nilai tipe selalu

vnd.android.cursor.dir

untuk beberapa baris, atau

vnd.android.cursor.item

untuk satu baris.

Subtipe adalah khusus penyedia. Penyedia bawaan Android biasanya memiliki subtipe sederhana. Misalnya, bila aplikasi Kontak membuat satu baris untuk nomor ponsel, aplikasi akan menyetel tipe MIME berikut di baris itu:

vnd.android.cursor.item/phone_v2

Perhatikan bahwa nilai subtipe adalah sekadar phone_v2.

Developer penyedia lain bisa membuat pola subtipe sendiri berdasarkan otoritas dan nama-nama tabel penyedia. Misalnya, perhatikan penyedia yang berisi jadwal kereta api. Otoritas penyedia adalah com.example.trains, dan berisi tabel-tabel Line1, Line2, dan Line3. Untuk merespons URI materi

content://com.example.trains/Line1

untuk tabel Line1, penyedia mengembalikan tipe MIME

vnd.android.cursor.dir/vnd.example.line1

Untuk merespons URI materi

content://com.example.trains/Line2/5

untuk baris 5 di tabel Line2, penyedia mengembalikan tipe MIME

vnd.android.cursor.item/vnd.example.line2

Kebanyakan penyedia materi mendefinisikan konstanta kelas kontrak untuk tipe MIME yang digunakannya. Kelas kontrak ContactsContract.RawContacts pada Penyedia Kontak misalnya, mendefinisikan konstanta CONTENT_ITEM_TYPE untuk tipe MIME baris kontak mentah tunggal.

URI materi untuk baris-baris tunggal dijelaskan di bagian URI Materi.