Створення провайдера контенту
Визначення контракту
Контент-провайдери (content providers) дають змогу звертатися одним додаткам до даних інших додатків. І ми також можемо зробити, щоб інші додатки могли звертатися до даних нашого додатка через деякий API. Для цього нам треба створити свій контент-провайдер. Розглянемо як це зробити.
Спочатку додамо в проєкт клас FriendsContract, який буде описувати основні значення, стовпці, адреси uri, використовувані в контент-провайдері.
public class FriendsContract {
static final String TABLE_NAME = "friends";
static final String CONTENT_AUTHORITY = "com.example.friendsprovider";
static final Uri CONTENT_AUTHORITY_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd." + CONTENT_AUTHORITY + "." + TABLE_NAME;
static final String CONTENT_ITEM_TYPE= "vnd.android.cursor.item/vnd." + CONTENT_AUTHORITY + "." + TABLE_NAME;
public static class Columns{
public static final String _ID = "_id";
public static final String NAME = "Name";
public static final String EMAIL = "Email";
public static final String PHONE = "Phone";
private Columns(){
}
}
static final Uri CONTENT_URI = Uri.withAppendedPath(CONTENT_AUTHORITY_URI, TABLE_NAME);
// створює uri за допомогою id
static Uri buildFriendUri(long taskId){
return ContentUris.withAppendedId(CONTENT_URI, taskId);
}
// отримує id з uri
static long getFriendId(Uri uri){
return ContentUris.parseId(uri);
}
}
За допомогою константи TABLE_NAME визначається ім'я таблиці, до якої відбуватиметься звернення. А вкладений статичний клас Columns описує стовпці цієї таблиці. Тобто таблиця називатиметься "friends", а стовпці - "_id", "Name", "Email", "Phone". Тобто, умовно кажучи, у таблиці зберігатимуться дані про друзів - ім'я, електронна адреса та номер телефону.
Константа CONTENT_AUTHORITY описує назву контент-провайдера. Тобто в моєму випадку провайдер називатиметься "com.example.friendsprovider". За допомогою імені провайдера створюється константа CONTENT_AUTHORITY_URI - універсальний локатор або своєрідний шлях, через який ми звертатимемося до провайдера під час виконання з ним різних операцій.
Також клас визначає дві константи CONTENT_TYPE і CONTENT_ITEM_TYPE, які визначають тип вмісту, що повертається. Тут є два варіанти: повернення набору даних і повернення одного об'єкта. Значення, що визначає набір даних, будується за принципом "vnd.android.cursor.dir/vnd.[name].[table]", де як [name] зазвичай виступає глобально унікальний ідентифікатор, наприклад, назва провайдера або ім'я пакета провайдера. А як [type], як правило, використовується ім'я таблиці. За схожою схемою будується друге значення, тільки замість "dir" ставиться "item".
Також у класі визначається допоміжна константа CONTENT_URI, яка описує шлях для доступу до таблиці friends. І також визначаємо два допоміжних методи: buildFriendUri() (повертає uri для доступу до об'єкта за певним id) і getFriendId (для вилучення id з переданого шляху uri).
Далі додамо в проєкт новий клас AppDatabase:
public class AppDatabase extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "friends.db";
public static final int DATABASE_VERSION = 1;
private static AppDatabase instance = null;
private AppDatabase(Context context){
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
static AppDatabase getInstance(Context context){
if(instance == null){
instance = new AppDatabase(context);
}
return instance;
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "CREATE TABLE " + FriendsContract.TABLE_NAME + "(" +
FriendsContract.Columns._ID + " INTEGER PRIMARY KEY NOT NULL, " +
FriendsContract.Columns.NAME + " TEXT NOT NULL, " +
FriendsContract.Columns.EMAIL + " TEXT, " +
FriendsContract.Columns.PHONE + " TEXT NOT NULL)";
db.execSQL(sql);
// додавання початкових даних
db.execSQL("INSERT INTO "+ FriendsContract.TABLE_NAME +" (" + FriendsContract.Columns.NAME
+ ", " + FriendsContract.Columns.PHONE + ") VALUES ('Tom', '+12345678990');");
db.execSQL("INSERT INTO "+ FriendsContract.TABLE_NAME +" (" + FriendsContract.Columns.NAME
+ ", " + FriendsContract.Columns.EMAIL + ", " + FriendsContract.Columns.PHONE +
" ) VALUES ('Bob', 'bob@gmail.com', '+13456789102');");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
Цей клас за патерном синглтона організовує доступ до бази даних і, крім того, створює саму базу даних і додає в неї початкові дані.
Додамо в проєкт, клас AppProvider, який власне і буде представляти провайдер контенту:
public class AppProvider extends ContentProvider {
private AppDatabase mOpenHelper;
private static final UriMatcher sUriMatcher = buildUriMatcher();
public static final int FRIENDS = 100;
public static final int FRIENDS_ID = 101;
private static UriMatcher buildUriMatcher() {
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
// content://com.example.friendsprovider/FRIENDS
matcher.addURI(FriendsContract.CONTENT_AUTHORITY, FriendsContract.TABLE_NAME, FRIENDS);
// content://com.example.friendsprovider/FRIENDS/8
matcher.addURI(FriendsContract.CONTENT_AUTHORITY, FriendsContract.TABLE_NAME + "/#", FRIENDS_ID);
return matcher;
}
@Override
public boolean onCreate() {
mOpenHelper = AppDatabase.getInstance(getContext());
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
final int match = sUriMatcher.match(uri);
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
switch(match) {
case FRIENDS:
queryBuilder.setTables(FriendsContract.TABLE_NAME);
break;
case FRIENDS_ID:
queryBuilder.setTables(FriendsContract.TABLE_NAME);
long taskId = FriendsContract.getFriendId(uri);
queryBuilder.appendWhere(FriendsContract.Columns._ID + " = " + taskId);
break;
default:
throw new IllegalArgumentException("Unknown URI: "+ uri);
}
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
return queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
final int match = sUriMatcher.match(uri);
switch(match){
case FRIENDS:
return FriendsContract.CONTENT_TYPE;
case FRIENDS_ID:
return FriendsContract.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI: "+ uri);
}
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
final int match = sUriMatcher.match(uri);
final SQLiteDatabase db;
Uri returnUri;
long recordId;
if (match == FRIENDS) {
db = mOpenHelper.getWritableDatabase();
recordId = db.insert(FriendsContract.TABLE_NAME, null, values);
if (recordId > 0) {
returnUri = FriendsContract.buildFriendUri(recordId);
} else {
throw new SQLException("Failed to insert: " + uri.toString());
}
} else {
throw new IllegalArgumentException("Unknown URI: " + uri);
}
return returnUri;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
final int match = sUriMatcher.match(uri);
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String selectionCriteria = selection;
if(match != FRIENDS && match != FRIENDS_ID)
throw new IllegalArgumentException("Unknown URI: "+ uri);
if(match==FRIENDS_ID) {
long taskId = FriendsContract.getFriendId(uri);
selectionCriteria = FriendsContract.Columns._ID + " = " + taskId;
if ((selection != null) && (selection.length() > 0)) {
selectionCriteria += " AND (" + selection + ")";
}
}
return db.delete(FriendsContract.TABLE_NAME, selectionCriteria, selectionArgs);
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
final int match = sUriMatcher.match(uri);
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String selectionCriteria = selection;
if(match != FRIENDS && match != FRIENDS_ID)
throw new IllegalArgumentException("Unknown URI: "+ uri);
if(match==FRIENDS_ID) {
long taskId = FriendsContract.getFriendId(uri);
selectionCriteria = FriendsContract.Columns._ID + " = " + taskId;
if ((selection != null) && (selection.length() > 0)) {
selectionCriteria += " AND (" + selection + ")";
}
}
return db.update(FriendsContract.TABLE_NAME, values, selectionCriteria, selectionArgs);
}
}
У підсумку вийде наступний проєкт:

Клас провайдера контенту має наслідуватись від абстрактного класу ContentProvider, який визначає низку методів для роботи з даними, зокрема, методи oncreate, query, insert, update, delete, getType.
Для побудови шляхів uri для запитів до джерела даних визначено об'єкт sUriMatcher, який представляє тип UriMatcher. Для його створення застосовується метод buildUriMatcher:
private static UriMatcher buildUriMatcher() {
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
// content://com.example.friendsprovider/FRIENDS
matcher.addURI(FriendsContract.CONTENT_AUTHORITY, FriendsContract.TABLE_NAME, FRIENDS);
// content://com.example.friendsprovider/FRIENDS/8
matcher.addURI(FriendsContract.CONTENT_AUTHORITY, FriendsContract.TABLE_NAME + "/#", FRIENDS_ID);
return matcher;
}
За допомогою методу addURI в об'єкт UriMatcher додається певний шлях uri, який використовується для надсилання запиту. Як перший параметр addUri приймає назву провайдера, який описується константою CONTENT_AUTHORITY. Другий параметр - шлях до даних у межах джерела даних - у цьому разі це таблиця friends. Третій параметр - числовий код, який дає змогу розмежувати характер операції. У цьому разі у нас можливі два типи запитів - для звернення до всієї таблиці, або для звернення до окремого об'єкта, незалежно від того, чи йдеться про додавання, отримання, оновлення або видалення даних. Тому додаються два uri. І для кожного використовується один із двох числових кодів - FRIENDS або FRIENDS_ID. Це можуть бути абсолютно будь-які числові коди. Але вони дадуть змогу потім дізнатися, йде запит до всієї таблиці загалом або до якогось одного певного об'єкта.
Метод oncreate() виконує початкову ініціалізацію провайдера під час його створення. У цьому випадку просто встановлюється використовувана база даних:
public boolean onCreate() {
mOpenHelper = AppDatabase.getInstance(getContext());
return true;
}
Отримання даних
Для отримання даних у провайдері визначено метод query().
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
final int match = sUriMatcher.match(uri);
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
switch(match){
case FRIENDS:
queryBuilder.setTables(FriendsContract.TABLE_NAME);
break;
case FRIENDS_ID:
queryBuilder.setTables(FriendsContract.TABLE_NAME);
long taskId = FriendsContract.getFriendId(uri);
queryBuilder.appendWhere(FriendsContract.Columns._ID + " = " + taskId);
break;
default:
throw new IllegalArgumentException("Unknown URI: "+ uri);
}
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
return queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
}
Цей метод має приймати п'ять параметрів:
uri: шлях запитуprojection: набір стовпців, дані для яких треба отриматиselection: вираз для вибірки типу«WHERE Name = ? ....»selectionArgs: набір значень для параметрів із selection (вставляються замість знаків запитання)- ``sortOrder`: критерій сортування, в якості якого виступає ім'я стовпця
За допомогою об'єкта SQLiteQueryBuilder створюємо запит sql, який буде виконуватися. Для цього спочатку отримуємо числовий код операції за допомогою виразу sUriMatcher.match(uri). Тобто тут ми дізнаємося, чи звернений запит до всієї таблиці (код FRIENDS) або до одного об'єкта (код FRIENDS_ID). Якщо запит звернений до всієї таблиці, то викликаємо метод queryBuilder.setTables(FriendsContract.TABLE_NAME).
Якщо запит йде до одного об'єкта, то в цьому випадку отримуємо власне ідентифікатор об'єкта і за допомогою методу appendWhere() додаємо умову для вибірки за цим ідентифікатором.
Наприкінці власне виконуємо запит за допомогою методу queryBuilder.query() і повертаємо об'єкт Cursor.
Далі ми розглянемо використання цього методу і курсору, який він повертає.
Додавання даних
Для додавання даних застосовується метод insert():
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
final int match = sUriMatcher.match(uri);
final SQLiteDatabase db;
Uri returnUri;
long recordId;
if (match == FRIENDS) {
db = mOpenHelper.getWritableDatabase();
recordId = db.insert(FriendsContract.TABLE_NAME, null, values);
if (recordId > 0) {
returnUri = FriendsContract.buildFriendUri(recordId);
} else {
throw new SQLException("Failed to insert: " + uri.toString());
}
} else {
throw new IllegalArgumentException("Unknown URI: " + uri);
}
return returnUri;
}
Метод приймає два параметри:
uri: шлях запитуvalues: об'єктContentValues, через який передаються дані, що додаються
Для виконання додавання виконується метод db.insert, який повертає ідентифікатор доданого об'єкта:
recordId = db.insert(TasksContract.TABLE_NAME, null, values);
За допомогою цього ідентифікатора створюється і повертається шлях Uri до створеного об'єкта.
Видалення даних
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
final int match = sUriMatcher.match(uri);
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String selectionCriteria = selection;
if(match != FRIENDS && match != FRIENDS_ID)
throw new IllegalArgumentException("Unknown URI: "+ uri);
if(match==FRIENDS_ID) {
long taskId = FriendsContract.getFriendId(uri);
selectionCriteria = FriendsContract.Columns._ID + " = " + taskId;
if ((selection != null) && (selection.length() > 0)) {
selectionCriteria += " AND (" + selection + ")";
}
}
return db.delete(FriendsContract.TABLE_NAME, selectionCriteria, selectionArgs);
}
Цей метод має приймати три параметри:
uri: шлях запитуselection: вираз для вибірки типу"WHERE Name = ? ...."selectionArgs: набір значень для параметрів з selection (вставляються замість знаків запитання)
Під час видалення ми можемо реалізувати один із двох сценаріїв: або видалити з таблиці набір даних (наприклад, друзів, у яких ім'я Том), або видалити один об'єкт за певним ідентифікатором. У разі якщо йде видалення за ідентифікатором, то до виразу вибірки даних, що видаляються, в selection додається умова видалення за id:
long taskId = FriendsContract.getFriendId(uri);
selectionCriteria = FriendsContract.Columns._ID + " = " + taskId;
if((selection != null) && (selection.length() > 0)){
selectionCriteria += " AND (" + selection + ")";
}
count = db.delete(FriendsContract.TABLE_NAME, selectionCriteria, selectionArgs);
Результатом видалення є кількість видалених рядків у таблиці.
Оновлення даних
Для оновлення даних застосовується метод update():
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
final int match = sUriMatcher.match(uri);
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String selectionCriteria = selection;
if(match != FRIENDS && match != FRIENDS_ID)
throw new IllegalArgumentException("Unknown URI: "+ uri);
if(match==FRIENDS_ID) {
long taskId = FriendsContract.getFriendId(uri);
selectionCriteria = FriendsContract.Columns._ID + " = " + taskId;
if ((selection != null) && (selection.length() > 0)) {
selectionCriteria += " AND (" + selection + ")";
}
}
return db.update(FriendsContract.TABLE_NAME, values, selectionCriteria, selectionArgs);
}
Цей метод має приймати чотири параметри:
uri: шлях запитуvalues: об'єктContentValues, який визначає нові значенняselection: вираз для вибірки типу"WHERE Name = ? ...."selectionArgs: набір значень для параметрів із selection (вставляються замість знаків запитання)
Метод update багато в чому аналогічний методу delete за тим винятком, що в метод передаються дані типу ContentValues, які передаються в метод db.update().
AndroidManifest
Але щоб провайдер контенту запрацював, необхідно внести зміни у файл AndroidManifest.xml. Наприклад, за замовчуванням цей файл має приблизно такий вигляд:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.friendsproviderapp">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.FriendsProviderApp">
<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>
І в кінець елемента <application> додамо визначення провайдера:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.friendsproviderapp">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.FriendsProviderApp">
<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:authorities="com.example.friendsprovider"
android:name="com.example.friendsproviderapp.AppProvider"
android:exported="false"/>
</application>
</manifest>
В елементі provider атрибут android:authorities вказує на назву провайдера - в даному випадку це назва, яка визначена в минулій темі в константі CONTENT_AUTHORITY в класі FriendsContract, тобто com.example.friendsprovider. А атрибут android:name вказує на повну назву класу провайдера з урахуванням його пакета. У моєму випадку пакет com.example.friendsproviderapp, а клас провайдера - AppProvider, тому в підсумку виходить com.example.friendsproviderapp.AppProvider.
Використання провайдера
Спочатку визначимо найпростіший візуальний інтерфейс для тестування можливостей провайдера у файлі activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/getButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Get"
android:onClick="getAll"
app:layout_constraintBottom_toTopOf="@id/addButton"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/addButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Add"
android:onClick="add"
app:layout_constraintBottom_toTopOf="@id/updateButton"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/getButton" />
<Button
android:id="@+id/updateButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Update"
android:onClick="update"
app:layout_constraintBottom_toTopOf="@id/deleteButton"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/addButton" />
<Button
android:id="@+id/deleteButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Delete"
android:onClick="delete"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/updateButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
Тут визначено набір кнопок для виведення списку друзів, а також додавання, оновлення та видалення. Кожна кнопка викликатиме відповідний метод у класі MainActivity.
Тепер змінимо код класу MainActivity. Для спрощення результати будемо виводити у вікні Logcat за допомогою методу Log.d():
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// отримання всіх
public void getAll(View view){
String[] projection = {
FriendsContract.Columns._ID,
FriendsContract.Columns.NAME,
FriendsContract.Columns.EMAIL,
FriendsContract.Columns.PHONE
};
ContentResolver contentResolver = getContentResolver();
Cursor cursor = contentResolver.query(FriendsContract.CONTENT_URI,
projection,
null,
null,
FriendsContract.Columns.NAME);
if(cursor != null){
Log.d(TAG, "count: " + cursor.getCount());
// перебор элементов
while(cursor.moveToNext()){
for(int i=0; i < cursor.getColumnCount(); i++){
Log.d(TAG, cursor.getColumnName(i) + " : " + cursor.getString(i));
}
Log.d(TAG, "=========================");
}
cursor.close();
}
else{
Log.d(TAG, "Cursor is null");
}
}
// Додавання
public void add(View view){
ContentResolver contentResolver = getContentResolver();
ContentValues values = new ContentValues();
values.put(FriendsContract.Columns.NAME, "Sam");
values.put(FriendsContract.Columns.EMAIL, "sam@gmail.com");
values.put(FriendsContract.Columns.PHONE, "+13676254985");
Uri uri = contentResolver.insert(FriendsContract.CONTENT_URI, values);
Log.d(TAG, "Friend added");
}
// Оновлення
public void update(View view){
ContentResolver contentResolver = getContentResolver();
ContentValues values = new ContentValues();
values.put(FriendsContract.Columns.EMAIL, "sammy@gmail.com");
values.put(FriendsContract.Columns.PHONE, "+55555555555");
String selection = FriendsContract.Columns.NAME + " = 'Sam'";
int count = contentResolver.update(FriendsContract.CONTENT_URI, values, selection, null);
Log.d(TAG, "Friend updated");
}
// Видалення
public void delete(View view){
ContentResolver contentResolver = getContentResolver();
String selection = FriendsContract.Columns.NAME + " = ?";
String[] args = {"Sam"};
int count = contentResolver.delete(FriendsContract.CONTENT_URI, selection, args);
Log.d(TAG, "Friend deleted");
}
}
Розберемо окремі дії, що виконуються в цьому коді.
Отримання даних
public void getAll(View view){
String[] projection = {
FriendsContract.Columns._ID,
FriendsContract.Columns.NAME,
FriendsContract.Columns.EMAIL,
FriendsContract.Columns.PHONE
};
ContentResolver contentResolver = getContentResolver();
Cursor cursor = contentResolver.query(FriendsContract.CONTENT_URI,
projection,
null,
null,
FriendsContract.Columns.NAME);
if(cursor != null){
Log.d(TAG, "count: " + cursor.getCount());
// перебор элементов
while(cursor.moveToNext()){
for(int i=0; i < cursor.getColumnCount(); i++){
Log.d(TAG, cursor.getColumnName(i) + " : " + cursor.getString(i));
}
Log.d(TAG, "=========================");
}
cursor.close();
}
else{
Log.d(TAG, "Cursor is null");
}
}
Взаємодія з провайдером контенту здійснюється через об'єкт ContentResolver. Для отримання даних викликається метод query(), по суті він являє собою виклик метод query провайдера контенту. У метод query передається uri - шлях до даних, projection - набір стовпчиків для вилучення, вираз вибірки і параметри для нього і назва стовпчика, за яким проводиться сортування.
Метод повертає курсор Cursor, який за допомогою методу moveToNext() можна перебрати і отримати окремі дані. Метод getColumnName() повертає назву стовпця, а getString() - власне значення цього стовпця:

Отримання одного об'єкта за id:
String[] projection = {
FriendsContract.Columns._ID,
FriendsContract.Columns.NAME,
FriendsContract.Columns.EMAIL,
FriendsContract.Columns.PHONE
};
ContentResolver contentResolver = getContentResolver();
Cursor cursor = contentResolver.query(FriendsContract.buildFriendUri(2),
projection, null, null, FriendsContract.Columns.NAME);
if(cursor != null){
while(cursor.moveToNext()){
for(int i=0; i < cursor.getColumnCount(); i++){
Log.d(TAG, cursor.getColumnName(i) + " : " + cursor.getString(i));
}
}
cursor.close();
}
У цьому випадку отримуємо об'єкт із _id=2.
Додавання даних
ContentResolver contentResolver = getContentResolver();
ContentValues values = new ContentValues();
values.put(FriendsContract.Columns.NAME, "Sam");
values.put(FriendsContract.Columns.EMAIL, "sam@gmail.com");
values.put(FriendsContract.Columns.PHONE, "+13676254985");
Uri uri = contentResolver.insert(FriendsContract.CONTENT_URI, values);
Для додавання застосовується метод insert, який приймає шлях URI і дані, що додаються, у вигляді ContentValues.
Оновлення даних
ContentResolver contentResolver = getContentResolver();
ContentValues values = new ContentValues();
values.put(FriendsContract.Columns.EMAIL, "sammy@gmail.com");
values.put(FriendsContract.Columns.PHONE, "+55555555555");
String selection = FriendsContract.Columns.NAME + " = 'Sam'";
int count = contentResolver.update(FriendsContract.CONTENT_URI, values, selection, null);
У цьому випадку оновлюються дані у всіх об'єктів, у яких "Name=Sam". Критерій оновлення передається через третій параметр.
Природно, за допомогою виразу SQL можна задати будь-яку логіку вибірки об'єктів для оновлення. І для більшої зручності ми можемо вводити в нього дані за допомогою параметрів, які задаються знаком питання:
ContentResolver contentResolver = getContentResolver();
ContentValues values = new ContentValues();
values.put(FriendsContract.Columns.NAME, "Sam");
String selection = FriendsContract.Columns.NAME + " = ?";
String args[] = {"Sam Scromby"};
int count = contentResolver.update(FriendsContract.CONTENT_URI, values, selection, args);
У цьому випадку за допомогою четвертого параметра передається масив значень для параметрів виразу вибірки.
Але в прикладах вище оновлювалися всі рядки в бд, які мали, наприклад, ім'я "Sam". Але також можна оновлювати й один об'єкт за id. Наприклад, оновимо рядок із _id=3:
ContentResolver contentResolver = getContentResolver();
ContentValues values = new ContentValues();
values.put(FriendsContract.Columns.NAME, "Sam");
values.put(FriendsContract.Columns.EMAIL, "sam@gmail.com");
int count = contentResolver.update(FriendsContract.buildFriendUri(3), values, null, null);
Видалення даних
Видалення даних за загальною умовою:
ContentResolver contentResolver = getContentResolver();
String selection = FriendsContract.Columns.NAME + " = ?";
String[] args = {"Sam"};
int count = contentResolver.delete(FriendsContract.CONTENT_URI, selection, args);
У цьому випадку видаляються всі рядки, у яких Name=Sam.
Видалення за id:
ContentResolver contentResolver = getContentResolver();
int count = contentResolver.delete(FriendsContract.buildFriendUri(2), null, null);
У цьому випадку видаляється рядок із _id=2.
Асинхронне завантаження даних
У базі даних може бути багато даних, і їх завантаження може зайняти деякий час. У цьому випадку можна скористатися асинхронним завантаженням даних. Для цього клас activity або фрагмента має реалізувати інтерфейс LoaderManager.LoaderCallbacks<Cursor>.
Візьмемо проєкт із минулої теми, де реалізовано провайдера контенту AppProvider, і змінимо клас MainActivity:
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String TAG = "MainActivity";
private static final int LOADER_ID = 225;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
// запускаємо завантаження даних через провайдера контенту
LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this);
}
@NonNull
@Override
public Loader<Cursor> onCreateLoader(int id, @Nullable Bundle args) {
String[] projection = {
FriendsContract.Columns._ID,
FriendsContract.Columns.NAME,
FriendsContract.Columns.EMAIL,
FriendsContract.Columns.PHONE
};
if(id == LOADER_ID)
return new CursorLoader(this, FriendsContract.CONTENT_URI,
projection,
null,
null,
FriendsContract.Columns.NAME);
else
throw new InvalidParameterException("Invalid loader id");
}
@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
if(data != null){
Log.d(TAG, "count: " + data.getCount());
// перебір елементів
while(data.moveToNext()){
for(int i=0; i < data.getColumnCount(); i++){
Log.d(TAG, data.getColumnName(i) + " : " + data.getString(i));
}
Log.d(TAG, "=========================");
}
data.close();
}
else{
Log.d(TAG, "Cursor is null");
}
}
@Override
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
Log.d(TAG, "onLoaderReset...");
}
}
Інтерфейс LoaderManager.LoaderCallbacks<Cursor> передбачає реалізацію трьох методів. Метод onCreateLoader() завантажує курсор. У цей метод як параметри передаються числовий код операції та об'єкт Bundle. Числовий код передається під час запуску завантаження курсора. В даному випадку в якості такого коду використовує константа LOADER_ID.
У самому методі створюється об'єкт CursorLoader. У його конструктор передається кілька параметрів:
- об'єкт
Context(поточнаactivity) - набір стовпців, які треба отримати
- вираз для вибірки даних
- значення для параметрів для виразу вибірки
- стовпець, за яким іде сортування
Метод onLoadFinished викликається при завантаженні курсору. Через другий параметр ми можемо власне отримати курсор і через нього завантажені дані. І відповідно в цьому методі ми може перебрати курсор, отримати дані і вивести їх в елементах графічного інтерфейсу або на консоль.
І метод onLoaderReset призначений для скидання завантажувача.
Щоб запустити завантаження даних, у методі onCreate викликається метод LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this);. Перший параметр - числовий код, а другий - об'єкт Bundle. Це ті значення, які ми можемо отримати в методі onCreateLoader. І третій - об'єкт Context.
У підсумку під час запуску MainActivity дані асинхронно будуть завантажені з бази даних:
