Android - ContentProviderを実装、および例

By JS | Last updated: September 01, 2020

ContentProviderは、アプリがデータを他のアプリと共有することを支援します。もし私のアプリでContentProviderを実装して提供する場合は、他のアプリは、ContentResolverを通し、私のアプリに実装されたContentProviderにアクセスすることができます。 ContentProviderはデータベースと同様にquery、insert、update、またはdeleteなどのAPIを提供しています。 ContentProvider interaction

他のアプリは、ContentProviderが提供するAPIを利用してデータを読み取り、保存、削除することができます。 もちろん、内部的にSQLiteなどのデータベースを使用してデータを管理する必要があります。 ContentProviderは、他のアプリとデータを共有するためのインタフェースと考えることができます。

ContentProvider宣言とアクセス制限

ContentProviderは AndroidManifest.xmlに次のように定義することができます。 android:authorities属性はContentProviderのIDと同じです。他のアプリで私のアプリのProviderを検索するときauthorityを知っている必要があります。

<provider
    android:name=".SampleContentProvider"
    android:authorities="com.example.contentprovidersample.provider"
    android:exported="true"
    android:permission="com.example.contentprovidersample.provider.READ_WRITE"/>

android:permission属性を定義しないと、私のエプマンContentProviderにアクセスすることができます。 この属性にパーミッションを設定すると、このパーミッションを持っているエプマン内アプリのContentProviderにアクセスすることができます。

ContentProviderアプローチ

ContentProviderはデータベースと同様にquery、insert、update、またはdeleteなどのAPIを提供しています。 アプリは、Providerのauthorityを知っていればContentProviderを使用してProviderにアクセスすることができます。

以下は、AppでProviderにデータをQueryするコードです。まず、authorityでUriを作成し、このUriをContentResolverの引数として渡してqueryすることができます。 APIの使用方法は、SQLiteなどのデータベースと似ています。 projection、selectionなどを設定して、必要なデータを見つけることができます。そして返されるCursorオブジェクトを介してデータを読み取ることができます。

companion object {
    const val TABLE_NAME = "cheeses"
    const val AUTHORITY = "com.example.contentprovidersample.provider"
    val URI_CHEESE: Uri = Uri.parse(
        "content://" + AUTHORITY + "/" + TABLE_NAME)
)

val cursor = contentResolver.query(
                SampleContentProvider.URI_CHEESE, // uri
                null, // projection
                null, // selection
                null, // selectionArgs
                Cheese.COLUMN_NAME // sortOrder
            )

Uriは content://というschemeにProviderのauthorityとtableの名前を組み合わせて作成します。

// 1
val URI_CHEESE_DIR: Uri =
  Uri.parse("content://com.example.contentprovidersample.provider/cheeses")

// 2
val URI_CHEESE_ITEM: Uri =
  Uri.parse("content://com.example.contentprovidersample.provider/cheeses/5")

上記のUriで // 1はテーブル全体を指すUri。 // 2は、最後にアイテムのIDを意味するIntegerがあります。 これはID 5の値を持っているアイテムを指すUri。

Uriによってアイテムに対してどのようなアクションを実行するかを決定することができます。

非同期的にアクセス

ActivityやFragmentでContentProviderに非同期的にアクセスするためにLoaderManager、CursorLoaderを使用することができます。 LoaderManagerはProviderのデータをすべて取得するとLoaderCallbacksを通じてCallbackをしてくれます。 また、追加、削除、更新、などでProviderのデータ変更があったときもCallbackをしてくれます。

CursorLoader, ContentResolver, ContentProvider

ContentProviderのデータ管理

ContentProviderは私のアプリのデータを外部のエプドゥルと接続するインターフェイスです。 実際のデータを保存して管理していません。したがって、ContentProviderは内部的にデータベースを使用してデータを管理する必要があります。 データベースはSQLite、MongoDBなどを使用することができます。また、Android ArchitectureのRoomを利用して実装することもできます。

次は、ContentProviderの query()メソッドです。内部的にSQLiteを使用してデータを管理しています。 (SampleDatabaseはSQLiteを抽象化したクラスです。コードは、次に紹介します。)

class SampleContentProvider : ContentProvider() {

  override fun query(
      uri: Uri,
      projection: Array<out String>?,
      selection: String?,
      selectionArgs: Array<out String>?,
      sortOrder: String?
  ): Cursor? {
        val queryBuilder = SQLiteQueryBuilder()
        queryBuilder.tables = Cheese.TABLE_NAME
        val db = SampleDatabase.getInstance(context!!)
        val cursor = queryBuilder.query(
            db, projection, selection, selectionArgs, null, null, sortOrder)

ContentProviderサンプルとチュートリアル

今からチュートリアル方式でContentProviderを実装する簡単なアプリを作ってみましょう。

この記事で実装するSampleは、GitHub - ContentProvider Sampleで確認することができます。

プロジェクトの作成

Empty Activityを選択します。 Project - Empty Activity

Kotlinを選択して、プロジェクトを作成します。 Project - kotlin

ContentProviderクラスの実装

ContentProviderを継承するProviderクラスを作成します。 基本的には次のメソッドをオーバーライドする必要があります。それぞれのメソッドは、SQLiteを使用して、データを処理します。

  • onCreate()
  • query()
  • insert()
  • update()
  • delete()
  • getTypte()

SampleContentProvider.ktファイルを作成し、次のように実装します。 ここSampleDatabaseというクラスは、SQLiteを抽象化したクラスです。実装については、次の紹介します。

class SampleContentProvider : ContentProvider() {

    companion object {
        const val AUTHORITY = "com.example.contentprovidersample.provider"
        val URI_CHEESE: Uri = Uri.parse(
            "content://" + AUTHORITY + "/" + Cheese.TABLE_NAME)
        const val CODE_CHEESE_DIR = 1
        const val CODE_CHEESE_ITEM = 2
    }

    private var uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
    init {
        uriMatcher.addURI(AUTHORITY, Cheese.TABLE_NAME, CODE_CHEESE_DIR)
        uriMatcher.addURI(AUTHORITY, "${Cheese.TABLE_NAME}/#", CODE_CHEESE_ITEM)
    }

    override fun onCreate(): Boolean {
        return true
    }

    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? {
        val code: Int = uriMatcher.match(uri)
        return when (uriMatcher.match(uri)) {
            CODE_CHEESE_DIR, CODE_CHEESE_ITEM -> {
                val queryBuilder = SQLiteQueryBuilder()
                queryBuilder.tables = Cheese.TABLE_NAME
                val db = SampleDatabase.getInstance(context!!)
                val cursor = queryBuilder.query(
                    db, projection, selection, selectionArgs, null, null, sortOrder)
                cursor.setNotificationUri(context!!.contentResolver, uri)
                cursor
            }
            else -> {
                throw java.lang.IllegalArgumentException("Unknown URI: $uri")
            }
        }
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        return when (uriMatcher.match(uri)) {
            CODE_CHEESE_DIR -> {
                val id = SampleDatabase.getInstance(context!!)
                    .insert(Cheese.TABLE_NAME, null, values)
                val insertedUri = ContentUris.withAppendedId(uri, id)
                context!!.contentResolver.notifyChange(insertedUri, null)
                insertedUri
            }
            CODE_CHEESE_ITEM -> {
                throw java.lang.IllegalArgumentException("Invalid URI, cannot insert with ID: $uri")
            }
            else -> {
                throw java.lang.IllegalArgumentException("Unknown URI: $uri")
            }
        }
    }

    override fun update(uri: Uri, values: ContentValues?, selection: String?,
                        selectionArgs: Array<out String>?): Int {
        return when (uriMatcher.match(uri)) {
            CODE_CHEESE_DIR -> {
                throw java.lang.IllegalArgumentException("Invalid URI, cannot update without ID$uri")
            }
            CODE_CHEESE_ITEM -> {
                val id = ContentUris.parseId(uri)
                val count = SampleDatabase.getInstance(context!!)
                    .update(Cheese.TABLE_NAME, values, "${Cheese.COLUMN_ID} = ?",
                        arrayOf(id.toString()))
                context!!.contentResolver.notifyChange(uri, null)
                count
            }
            else -> {
                throw java.lang.IllegalArgumentException("Unknown URI: $uri")
            }
        }
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        return when (uriMatcher.match(uri)) {
            CODE_CHEESE_DIR -> {
                throw java.lang.IllegalArgumentException("Invalid URI, cannot update without ID: $uri")
            }
            CODE_CHEESE_ITEM -> {
                val id = ContentUris.parseId(uri)
                val count = SampleDatabase.getInstance(context!!)
                    .delete(Cheese.TABLE_NAME,
                        "${Cheese.COLUMN_ID} = ?",
                        arrayOf(id.toString()))
                context!!.contentResolver.notifyChange(uri, null)
                count
            }
            else -> {
                throw java.lang.IllegalArgumentException("Unknown URI: $uri")
            }
        }
    }

    override fun getType(uri: Uri): String? {
        return when (uriMatcher.match(uri)) {
            CODE_CHEESE_DIR -> "vnd.android.cursor.dir/$AUTHORITY.$Cheese.TABLE_NAME"
            CODE_CHEESE_ITEM -> "vnd.android.cursor.item/$AUTHORITY.$Cheese.TABLE_NAME"
            else -> throw IllegalArgumentException("Unknown URI: $uri")
        }
    }
}

Constantsは Cheese.ktファイルを作成し、ここに定義しました。

class Cheese {
    companion object {
        const val TABLE_NAME = "cheeses"
        const val COLUMN_ID = BaseColumns._ID
        const val COLUMN_NAME = "name"

        val CHEESES = arrayOf(
            "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
            "Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon"
        )
    }
}

上記のコードで重要な部分をそれぞれ説明します。

UriMatcher

次のコードは、UriMatcherを定義するコードです。 UriMatcherはUriが一致するか否かを判断するUtilityクラスです。 UriMatcher.addURI()は比較したいUriを登録するメソッドです。 最後の引数は、 UriMatcher.match()の結果、引数として渡されたUriと一致したときに戻されるIntegerです。 もし一致するUriがない場合は-1を返します。

const val TABLE_NAME = "cheeses"
const val CODE_CHEESE_DIR = 1
const val CODE_CHEESE_ITEM = 2
private var uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
init {
    uriMatcher.addURI(AUTHORITY, TABLE_NAME, CODE_CHEESE_DIR)
    uriMatcher.addURI(AUTHORITY, "${TABLE_NAME}/#", CODE_CHEESE_ITEM)
}

例えば上で定義しUriMatcherを利用した時、次のコードのUriの match()CODE_CHEESE_DIRを返します。

val uri = Uri.parse("content://com.example.contentprovidersample.provider/cheeses")
uriMatcher.match(uri)

他の例として、次のコードのUriの match()CODE_CHEESE_ITEMを返します。

val uri = Uri.parse("content://com.example.contentprovidersample.provider/cheeses/5")
uriMatcher.match(uri)

UriMatcherの詳細については、Android Developer - Content uri patternsページをご確認ください。

UriMatcherを使用する理由

UriMatcherは次のように使用することができます。次のコードでUriがテーブル全体を意味する ..../cheeses形で入ってきた場合、insertを実行して、 ..../cheeses/5よう、特定のIDが入力された形でUriが引数として渡された場合、Exceptionを発生させるように実装することができます。

override fun insert(uri: Uri, values: ContentValues?): Uri? {
    return when (uriMatcher.match(uri)) {
        CODE_CHEESE_DIR -> {
            val id = SampleDatabase.getInstance(context!!)
                .insert(Cheese.TABLE_NAME, null, values)
            val insertedUri = ContentUris.withAppendedId(uri, id)
            context!!.contentResolver.notifyChange(insertedUri, null)
            insertedUri
        }
        CODE_CHEESE_ITEM -> {
            throw java.lang.IllegalArgumentException("Invalid URI, cannot insert with ID: $uri")
        }
        else -> {
            throw java.lang.IllegalArgumentException("Unknown URI: $uri")
        }
    }
}

ManifestにContentProvider定義

ContentProviderを実装したら、AppのAndroidManifestに定義する必要があります。

外部アプリからこのProviderへのアクセスを必要がある場合パーミッション属性を定義する必要があります。パーミッションを持っているエプマンアクセスすることができるからです。

<permission android:name="com.example.contentprovidersample.provider.READ_WRITE"/>

<application
  <provider
      android:name=".SampleContentProvider"
      android:authorities="com.example.contentprovidersample.provider"
      android:exported="true"
      android:permission="com.example.contentprovidersample.provider.READ_WRITE"/>
</application>

上記のManifestでパーミッションを定義したがね。このパーミッションのProtectionLevelはnormalです。つまり、誰でも <uses-permission>でそのパーミッションを使用すると定義すると、得ることができます。 Providerは、常に開いていないが、私Providerが要求するパーミッションを簡単に知ることができますので、必要な場合パーミッションを設定して、アクセスすることができます。もしアプリと同じsignatureに構築されたエプマン権限を与えたい場合はProtectionLevelをsignatureに設定ください。

データベース(SQLite)の実装

このSampleでデータベースはSQLiteを使用しました。データベースの実装は、 SampleDatabaseクラスに抽象化しました。

SampleDatabase.ktファイルを作成し、次のように実装します。

class SampleDatabase {
    companion object {
        private const val DATABASE_NAME = "cheese.db"
        private const val DATABASE_VERSION = 1
        private var sInstance: DatabaseHelper? = null

        @Synchronized
        fun getInstance(context: Context): SQLiteDatabase {
            if (sInstance == null) {
                sInstance = DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION)
            }
            return sInstance!!.writableDatabase
        }

        class DatabaseHelper(
            context: Context?,
            name: String?,
            factory: SQLiteDatabase.CursorFactory?,
            version: Int) : SQLiteOpenHelper(context, name, factory, version) {
            override fun onCreate(_db: SQLiteDatabase?) {
                _db?.execSQL("CREATE TABLE ${Cheese.TABLE_NAME}"
                        + " (${Cheese.COLUMN_ID} INTEGER PRIMARY KEY AUTOINCREMENT, "
                        + " ${Cheese.COLUMN_NAME} TEXT NOT NULL);")
            }

            override fun onUpgrade(_db: SQLiteDatabase?, _oldVersion: Int, _newVersion: Int) {
                _db?.execSQL("DROP TABLE IF EXISTS ${Cheese.TABLE_NAME}")
                onCreate(_db)
            }
        }
    }
}

コードを見れば SQLiteDatabaseSQLiteOpenHelperなどを利用して実装しました。 Singleton実装したので、 SampleDatabase.getInstance()でオブジェクトを取得することができます。

ContentProviderを紹介する文であるため、SQLiteのは詳しく説明しませんでした。

Activityの実装

今ContentProvider、Databaseをすべて実装しました。今私のアプリでは、Providerにアクセスするコードを実装することです。

MainActivity.ktのlayoutあるactivity_main.xmlを次のように修正してくれます。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/buttonLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="5dp">
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Add"
            android:onClick="addItem">
        </Button>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Update"
            android:onClick="updateItem">
        </Button>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Remove"
            android:onClick="removeItem">
        </Button>
    </LinearLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:scrollbars="vertical"/>

</LinearLayout>

MainActivity.ktは次のように変更します。

class MainActivity : AppCompatActivity() {

    companion object {
        const val TAG = "MainActivity"
        const val LOADER_CHEESES = 1
    }
    private var cheeseAdapter: CheeseAdapter = CheeseAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        populateInitialDataIfNeeded()

        val list = findViewById<RecyclerView>(R.id.list)
        list.layoutManager = LinearLayoutManager(list.context)
        list.adapter = cheeseAdapter

        LoaderManager.getInstance(this)
            .initLoader(`LOADER_CHEESES`, null, loaderCallbacks)
    }

    private val loaderCallbacks: LoaderManager.LoaderCallbacks<Cursor> =
        object : LoaderManager.LoaderCallbacks<Cursor> {
            override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor?> {
                return CursorLoader(
                    applicationContext,
                    SampleContentProvider.URI_CHEESE, // uri
                    arrayOf<String>(Cheese.COLUMN_NAME), // projection
                    null, // selection
                    null, // selectionArgs
                    Cheese.COLUMN_NAME // sortOrder
                )
            }

            override fun onLoadFinished(loader: Loader<Cursor?>, data: Cursor?) {
                cheeseAdapter.setCheeses(data)
            }

            override fun onLoaderReset(loader: Loader<Cursor?>) {
                cheeseAdapter.setCheeses(null)
            }
        }

    internal class CheeseAdapter : RecyclerView.Adapter<CheeseAdapter.ViewHolder?>() {
        private var cursor: Cursor? = null

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            return ViewHolder(parent)
        }

        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            if (cursor!!.moveToPosition(position)) {
                holder.text.text = cursor!!.getString(
                    cursor!!.getColumnIndexOrThrow(Cheese.COLUMN_NAME)
                )
            }
        }

        fun setCheeses(cursor: Cursor?) {
            this.cursor = cursor
            notifyDataSetChanged()
        }

        internal class ViewHolder(parent: ViewGroup) :
            RecyclerView.ViewHolder(
                LayoutInflater.from(parent.context).inflate(
                    android.R.layout.simple_list_item_1, parent, false
                )
            ) {
            val text: TextView = itemView.findViewById(android.R.id.text1)
        }

        override fun getItemCount(): Int {
            return if (cursor == null) {
                0
            } else {
                cursor!!.count
            }
        }
    }

    private fun populateInitialDataIfNeeded() {
        val cursor = contentResolver.query(
            SampleContentProvider.URI_CHEESE,
            null,
            null,
            null,
            null
        )
        if (cursor != null && cursor.count == 0) {
            Log.d(TAG, "Add initial data")
            for (cheese in  Cheese.CHEESES) {
                val values = ContentValues()
                values.put(Cheese.COLUMN_NAME, cheese)
                contentResolver.insert(SampleContentProvider.URI_CHEESE, values)
            }
        }
    }

    fun addItem(view: View) {
        val values = ContentValues()
        values.put(Cheese.COLUMN_NAME, "New Item")
        val uri = contentResolver.insert(SampleContentProvider.URI_CHEESE, values)
        Log.d(TAG, "Added item: $uri")
    }

    fun updateItem(view: View) {
        val uri = queryAndGetOne()
        if (uri != null) {
            Log.d(TAG, "Update item: $uri")
            val values = ContentValues()
            values.put(Cheese.COLUMN_NAME, "Updated Item")
            contentResolver.update(uri, values, null, null)
        }
    }

    fun removeItem(view: View) {
        val uri = queryAndGetOne()
        if (uri != null) {
            Log.d(TAG, "Remove item: $uri")
            contentResolver.delete(
                uri,
                null,
                null
            )
        }
    }

    private fun queryAndGetOne() : Uri? {
        val cursor = contentResolver.query(
            SampleContentProvider.URI_CHEESE, // uri
            null, // projection
            null, // selection
            null, // selectionArgs
            Cheese.COLUMN_NAME // sortOrder
        )
        return if (cursor != null && cursor.count != 0) {
            cursor.moveToFirst()
            val id = cursor.getString(cursor.getColumnIndex(Cheese.COLUMN_ID))
            val name = cursor.getString(cursor.getColumnIndex(Cheese.COLUMN_NAME));
            val uri = ContentUris.withAppendedId(SampleContentProvider.URI_CHEESE, id.toLong())
            Log.d(TAG, "query and return uri: $uri (id: $id, name: $name)")
            uri
        } else {
            null
        }
    }
}

上記のコードでRecyclerViewを使用するため、アプリの build.gradleの依存性にRecyclerViewライブラリを追加する必要があります。

dependencies {
    ...
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
}

LoaderManager, CursorLoader

LoaderManagerは非同期的にContentProviderのデータのインポートに役立つクラスです。 LoaderManagerに多数のLoaderを登録することができ、CursorLoaderを利用して、ContentProviderのデータをインポートすることができます。

次のコードで onCreateLoader()はCursorLoaderを定義します。 CursorLoaderはどのプロバイダを読むかUri情報とqueryに必要なselectionなどの情報を引数として渡します。 queryが完了すると、 onLoadFinished()でデータが渡されます。 Cursorからデータを読み取るRecyclerViewにアイテムが見えるように実装するされます。

companion object {
    const val LOADER_CHEESES = 1
}

override fun onCreate(savedInstanceState: Bundle?) {
    ...

    LoaderManager.getInstance(this)
        .initLoader(`LOADER_CHEESES`, null, loaderCallbacks)
}

private val loaderCallbacks: LoaderManager.LoaderCallbacks<Cursor> =
    object : LoaderManager.LoaderCallbacks<Cursor> {
        override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor?> {
            return CursorLoader(
                applicationContext,
                SampleContentProvider.URI_CHEESE, // uri
                arrayOf<String>(Cheese.COLUMN_NAME), // projection
                null, // selection
                null, // selectionArgs
                Cheese.COLUMN_NAME // sortOrder
            )
        }

        override fun onLoadFinished(loader: Loader<Cursor?>, data: Cursor?) {
            cheeseAdapter.setCheeses(data)
        }

        override fun onLoaderReset(loader: Loader<Cursor?>) {
            cheeseAdapter.setCheeses(null)
        }
    }

初期値の追加

アプリを起動し、データベースにデータがないため、動作を確認するのに良くはありません。 次のようなコードで、データがない場合にdummyデータを追加するようにしました。

private fun populateInitialDataIfNeeded() {
    val cursor = contentResolver.query(
        SampleContentProvider.URI_CHEESE,
        null,
        null,
        null,
        null
    )
    if (cursor != null && cursor.count == 0) {
        Log.d(TAG, "Add initial data")
        for (cheese in  Cheese.CHEESES) {
            val values = ContentValues()
            values.put(Cheese.COLUMN_NAME, cheese)
            contentResolver.insert(SampleContentProvider.URI_CHEESE, values)
        }
    }
}

insert(), update(), remove()

それぞれのボタンが押されると、insert、update、またはremoveを呼び出すコードを実装しました。

fun addItem(view: View) {
    val values = ContentValues()
    values.put(Cheese.COLUMN_NAME, "New Item")
    val uri = contentResolver.insert(SampleContentProvider.URI_CHEESE, values)
    Log.d(TAG, "Added item: $uri")
}

fun updateItem(view: View) {
    val uri = queryAndGetOne()
    if (uri != null) {
        Log.d(TAG, "Update item: $uri")
        val values = ContentValues()
        values.put(Cheese.COLUMN_NAME, "Updated Item")
        contentResolver.update(uri, values, null, null)
    }
}

fun removeItem(view: View) {
    val uri = queryAndGetOne()
    if (uri != null) {
        Log.d(TAG, "Remove item: $uri")
        contentResolver.delete(
            uri,
            null,
            null
        )
    }
}

updateとremoveは、すでに保存されたアイテムに対して実行を説得する。 次のように COLUMN_NAME columnに配置されたデータの中で最も最初のアイテムを使用するように実装しました。

private fun queryAndGetOne() : Uri? {
    val cursor = contentResolver.query(
        SampleContentProvider.URI_CHEESE, // uri
        null, // projection
        null, // selection
        null, // selectionArgs
        Cheese.COLUMN_NAME // sortOrder
    )
    return if (cursor != null && cursor.count != 0) {
        cursor.moveToFirst()
        val id = cursor.getString(cursor.getColumnIndex(Cheese.COLUMN_ID))
        val name = cursor.getString(cursor.getColumnIndex(Cheese.COLUMN_NAME));
        val uri = ContentUris.withAppendedId(SampleContentProvider.URI_CHEESE, id.toLong())
        Log.d(TAG, "query and return uri: $uri (id: $id, name: $name)")
        uri
    } else {
        null
    }
}

まとめ

Room with Content Providers Sampleを参照してContentProviderを簡単に実装してみました。参考にしたSampleはRoomを使用して、Javaで実装されていますが、これをSQLiteに変更し、kotlinに実装しました。

この記事で紹介されたSampleは、GitHub - ContentProvider Sampleで確認することができます。

参考

Related Posts

codechachaCopyright ©2019 codechacha