В этом уроке:

- используем CursorLoader

 

В Уроке 52 использованы устаревшие на данный момент методы запроса данных от БД и связки Activity с Cursor.  Вместо них рекомендуется использовать CursorLoader, который будет асинхронно читать данные и возвращать Cursor. Этот урок будет являться копией Урока 52 только с использованием CursorLoader.

CursorLoader представляет собой наследника класса AsyncTaskLoader<Cursor> и по умолчанию заточен на работу с ContentProvider, т.к. при работе требует Uri. Мы же в этом примере используем его для работы со своей БД. Для этого нам придется его расширить и вставить свою реализацию в его основной метод.

Приложение урока - это список, который отображает содержимое БД. Кнопкой можно записи добавлять, а контекстным меню - удалять.

 

Создадим проект:

Project name: P1361_CursorLoader
Build Target: Android 2.3.3
Application name: CursorLoader
Package name: ru.startandroid.develop.p1361cursorloader
Create Activity: MainActivity

 

В strings.xml добавим строки:

<string name="add_record">Добавить запись</string>
<string name="delete_record">Удалить запись</string>

 

Экран main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onButtonClick"
        android:text="@string/add_record">
    </Button>
    <ListView
        android:id="@+id/lvData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </ListView>
</LinearLayout>

Кнопка добавления записи и список

 

Layout пункта списка item.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <ImageView
        android:id="@+id/ivImg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher">
    </ImageView>
    <TextView
        android:id="@+id/tvText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"
        android:text=""
        android:textSize="18sp">
    </TextView>
</LinearLayout>

Статичная картинка и текст.

 

Работу с БД вынесем в отдельный класс DB.java:

package ru.startandroid.develop.p1361cursorloader;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

public class DB {
  
  private static final String DB_NAME = "mydb";
  private static final int DB_VERSION = 1;
  private static final String DB_TABLE = "mytab";
  
  public static final String COLUMN_ID = "_id";
  public static final String COLUMN_IMG = "img";
  public static final String COLUMN_TXT = "txt";
  
  private static final String DB_CREATE = 
    "create table " + DB_TABLE + "(" +
      COLUMN_ID + " integer primary key autoincrement, " +
      COLUMN_IMG + " integer, " +
      COLUMN_TXT + " text" +
    ");";
  
  private final Context mCtx;
  
  
  private DBHelper mDBHelper;
  private SQLiteDatabase mDB;
  
  public DB(Context ctx) {
    mCtx = ctx;
  }
  
  // открыть подключение
  public void open() {
    mDBHelper = new DBHelper(mCtx, DB_NAME, null, DB_VERSION);
    mDB = mDBHelper.getWritableDatabase();
  }
  
  // закрыть подключение
  public void close() {
    if (mDBHelper!=null) mDBHelper.close();
  }
  
  // получить все данные из таблицы DB_TABLE
  public Cursor getAllData() {
    return mDB.query(DB_TABLE, null, null, null, null, null, null);
  }
  
  // добавить запись в DB_TABLE
  public void addRec(String txt, int img) {
    ContentValues cv = new ContentValues();
    cv.put(COLUMN_TXT, txt);
    cv.put(COLUMN_IMG, img);
    mDB.insert(DB_TABLE, null, cv);
  }
  
  // удалить запись из DB_TABLE
  public void delRec(long id) {
    mDB.delete(DB_TABLE, COLUMN_ID + " = " + id, null);
  }
  
  // класс по созданию и управлению БД
  private class DBHelper extends SQLiteOpenHelper {

    public DBHelper(Context context, String name, CursorFactory factory,
        int version) {
      super(context, name, factory, version);
    }

    // создаем и заполняем БД
    @Override
    public void onCreate(SQLiteDatabase db) {
      db.execSQL(DB_CREATE);
      
      ContentValues cv = new ContentValues();
      for (int i = 1; i < 5; i++) {
        cv.put(COLUMN_TXT, "sometext " + i);
        cv.put(COLUMN_IMG, R.drawable.ic_launcher);
        db.insert(DB_TABLE, null, cv);
      }
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
  }
}

Здесь создание БД, управление подключением и методы по чтению/добавлению/удалению записей.

 

 

MainActivity.java:

package ru.startandroid.develop.p1361cursorloader;

import java.util.concurrent.TimeUnit;

import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ListView;

public class MainActivity extends FragmentActivity implements LoaderCallbacks<Cursor> {

  private static final int CM_DELETE_ID = 1;
  ListView lvData;
  DB db;
  SimpleCursorAdapter scAdapter;

  /** Called when the activity is first created. */
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // открываем подключение к БД
    db = new DB(this);
    db.open();
    
    // формируем столбцы сопоставления
    String[] from = new String[] { DB.COLUMN_IMG, DB.COLUMN_TXT };
    int[] to = new int[] { R.id.ivImg, R.id.tvText };

    // создаем адаптер и настраиваем список
    scAdapter = new SimpleCursorAdapter(this, R.layout.item, null, from, to, 0);
    lvData = (ListView) findViewById(R.id.lvData);
    lvData.setAdapter(scAdapter);

    // добавляем контекстное меню к списку
    registerForContextMenu(lvData);
    
    // создаем лоадер для чтения данных
    getSupportLoaderManager().initLoader(0, null, this);
  }

  // обработка нажатия кнопки
  public void onButtonClick(View view) {
    // добавляем запись
    db.addRec("sometext " + (scAdapter.getCount() + 1), R.drawable.ic_launcher);
    // получаем новый курсор с данными
    getSupportLoaderManager().getLoader(0).forceLoad();
  }
  
  public void onCreateContextMenu(ContextMenu menu, View v,
      ContextMenuInfo menuInfo) {
    super.onCreateContextMenu(menu, v, menuInfo);
    menu.add(0, CM_DELETE_ID, 0, R.string.delete_record);
  }

  public boolean onContextItemSelected(MenuItem item) {
    if (item.getItemId() == CM_DELETE_ID) {
      // получаем из пункта контекстного меню данные по пункту списка
      AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) item
          .getMenuInfo();
      // извлекаем id записи и удаляем соответствующую запись в БД
      db.delRec(acmi.id);
      // получаем новый курсор с данными
      getSupportLoaderManager().getLoader(0).forceLoad();
      return true;
    }
    return super.onContextItemSelected(item);
  }

  protected void onDestroy() {
    super.onDestroy();
    // закрываем подключение при выходе
    db.close();
  }

  @Override
  public Loader<Cursor> onCreateLoader(int id, Bundle bndl) {
    return new MyCursorLoader(this, db);
  }

  @Override
  public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    scAdapter.swapCursor(cursor);
  }

  @Override
  public void onLoaderReset(Loader<Cursor> loader) {
  }
  
  static class MyCursorLoader extends CursorLoader {

    DB db;
    
    public MyCursorLoader(Context context, DB db) {
      super(context);
      this.db = db;
    }
    
    @Override
    public Cursor loadInBackground() {
      Cursor cursor = db.getAllData();
      try {
        TimeUnit.SECONDS.sleep(3);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      return cursor;
    }
    
  }
}

В onCreate подключаемся к БД, создаем SimpleCursorAdapter, добавляем контекстное меню к списку и создаем CursorLoader. Я везде для лоадера буду использовать ID = 0.

В onButtonClick добавляем запись в БД, получаем лоадер и просим его получить для нас новый курсор с данными.

onCreateContextMenu – создание контекстного меню.

В onContextItemSelected мы реализуем удаление записи из БД. И после удаления снова просим лоадер дать нам новый курсор с данными.

В onDestroy отключаемся от БД.

 

Далее идут колбэк-методы интерфейса LoaderCallbacks.

В onCreateLoader создаем Loader и даем ему на вход объект для работы с БД.

В onLoadFinished мы получаем результат работы лоадера – новый курсор с данными. Этот курсор мы отдаем адаптеру методом swapCursor.

 

MyCursorLoader – наш лоадер, наследник класса CursorLoader. У него мы переопределяем метод loadInBackground, в котором просто получаем курсор с данными БД. Ну и я 3-х секундной паузой сэмулировал долгое чтение БД для наглядности асинхронной работы.

Все сохраняем, запускаем пример. Работает и добавление по нажатию кнопки и удаление через контекстное меню. Работает с задержкой в 3 секунды, но при этом не тормозит интерфейс, т.к. работа выполняется асинхронно.

 

Кроме асинхронной загрузки, CursorLoader:

- закрывает старый курсор при успешном получении нового
- закрывает курсор при уничтожении лоадера (т.е. и при выходе из приложения)
- при переходе в состояние «стартован» проверяет метку, которую ставит Observer и запускает работу, если данные изменились
- при переходе в состояние «стартован» стартует работу, если еще не было получено никаких результатов (например при первом запуске).

 

На следующем уроке:

- читаем данные с сенсоров


Присоединяйтесь к нам в Telegram:

- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance 

- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня




Language

Автор сайта

Дмитрий Виноградов

Подробнее можно посмотреть или почитать.

Никакие другие люди не имеют к этому сайту никакого отношения и просто занимаются плагиатом.

Социальные сети

 

В канале я публикую ссылки на интересные и полезные статьи по Android

В чате можно обсудить вопросы и проблемы, возникающие при разработке



Группа ВКонтакте



Поддержка проекта

Яндекс
410011180491924

WebMoney
R248743991365
Z551306702056

Paypal