Lesson 136. CursorLoader


In this lesson:

– we use CursorLoader

Lesson 52 uses the currently obsolete database query methods and Activity links with Cursor. Instead, we recommend using CursorLoader, which will read data asynchronously and return Cursor. This lesson will be a copy of Lesson 52 only using CursorLoader.

CursorLoader is the heir to the AsyncTaskLoader class and by default sharpened to work with ContentProvider because it requires Uri at work. In this example, we use it to work with our database. To do this, we will have to extend it and insert our implementation into its main method.

The lesson appendix is ​​a list that displays the contents of the database. The button can be used to add entries and the context menu can be deleted.

Let’s create a project:

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

IN strings.xml add rows:

Добавить запись
Удалить запись

screen main.xml:



    
    
    

Add record button and list

Layout a list item item.xml:



    
    
    
    

Static picture and text.

The work with the database will be made in a separate class 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) {
    }
  }
}

This is where database creation, connection management, and methods for reading / adding / deleting records are created.

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 {

  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 onCreateLoader(int id, Bundle bndl) {
    return new MyCursorLoader(this, db);
  }

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

  @Override
  public void onLoaderReset(Loader 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;
    }
    
  }
}

IN onCreate connect to the database, create a SimpleCursorAdapter, add a context menu to the list, and create a CursorLoader. I'm going to use ID = 0 everywhere for the launcher.

IN onButtonClick add a record to the database, get a loader and ask him to get a new data cursor for us.

onCreateContextMenu - creating a context menu.

IN onContextItemSelected we are removing database records. And after deleting again, we ask the loader to give us a new data cursor.

IN onDestroy disconnect from the database.

Following are the loadback methods of the LoaderCallbacks interface.

IN onCreateLoader create a Loader and give it an input object to work with the database.

IN onLoadFinished we get the result of the work of the loader - a new cursor with data. We give this cursor to the adapter using the swapCursor method.

MyCursorLoader is our Loader, heir to the CursorLoader class. We have a predefined method loadInBackgroundIn which we just get the cursor with the database data. Well, I have a 3-second pause to simulate a long DB reading for clarity of asynchronous operation.

We save everything, run the example. Also works after adding a button and removing it through the context menu. Works with a delay of 3 seconds, but it does not slow down the interface, because the work is done asynchronously.

In addition to asynchronous loading, CursorLoader:

- closes the old cursor when successfully receiving a new one
- closes the cursor when destroying the launcher (ie when exiting the program)
- when switching to "start" state, it checks the label put by Observer and starts working if the data has changed
- when switching to the "started" state, the job starts, if no results have been received (for example, at the first start).

In the next lesson:

- read data from sensors




Discuss in the forum [372 replies]

Leave a Comment