Search code examples
androidandroid-loadermanager

How do I use a CursorLoader to show individual database records one by one


I'm trying to make a ContentProvider with each record containing a question and corresponding answer. And an Activity showing TextViews of each question and answer and a "Next" button below those TextViews. When the Next button is clicked I would like the next question and answer to show.

I'm trying to use a CursorLoader and LoaderManager, because the CursorLoaders keep their data across onStop() and onStart(), and I am trying to learn about CursorLoaders and LoaderManagers.

The examples I have found all use setListAdapter(), but I don't want my activity to look like a list. I've tried to go around this by using a SimpleCursorAdapter and using bindView() to my main.xml layout. Not sure that is going to work.

If I had a plain Cursor I would use moveToNext(), but for a LoaderManager it seems I have to restartLoader() with a new query. I think creating a new query would cost more time than simply going to the next record with a cursor. Especially since I would have to know the position of the current or next record.

So my question is: Can I use a CursorLoader and LoaderManager to go through a database, record by record without having to make a new query for the next record? Or are CursorLoaders and LoaderManagers really only for ListViews?

Here is my code so far, I realize it's not much, but I have read and re-read Android's pages on Loaders and LoadManagers.

 public class Main extends Activity implements LoaderManager.LoaderCallbacks<Cursor>{

SimpleCursorAdapter adapter;
String curFilter;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    this.adapter = new SimpleCursorAdapter(getApplicationContext(),
                                           R.layout.main,
                                           null,
                                           new String[]{},
                                           new int[]{R.id.questionText,R.id.answerText},
                                           0);
    LinearLayout mainLayout = (LinearLayout)findViewById(R.id.mainLayout);

    this.adapter.bindView(mainLayout, getApplicationContext(), null);

    this.getLoaderManager().initLoader(0,null, this);

    Button nextQuestionButton = (Button)this.findViewById(R.id.Nextbutton);

    nextQuestionButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick (View v) {
        }               
    });
}

Solution

  • I understand that my question was vague, I was really lost. Here's my answer, many months later, hope it helps someone who is in my past position.

    onCreateLoader() is automatically called when I call getLoaderManager().initLoader() or when there has been a change to the content provider (content provider has to call getContentResolver().notifyChange() for this to work). I provide the code to make the cursor loader when overwriting the method LoaderManager.LoaderCallbacks.onCreateLoader(). The cursor loader is automatically handed to onLoadFinished(). That's it, I don't touch the cursor loader again. onLoadFinished(), which is called automatically, receives a cursor (made from the cursor loader) as an argument. I update the adapter in my override of onLoadFinished() using the cursor argument, this.adapter.setCursor(cursor).

    SimpleCursorAdapter doesn't have a moveToNext or moveToPrevious, so I made a SteppedAdapter, see below:

    public class SteppedAdapter {
        private Cursor cursor = null;
        // This class uses a reference which may be changed
        // by the calling class.
        public SteppedAdapter (Cursor cursor) {
        this.cursor = cursor;
        }
    
        private int getColumnIndexOrThrow (String columnName) {
        return cursor.getColumnIndexOrThrow(columnName);
        }   
    
        public void moveToNext () {     
        if (null != cursor && !cursor.isClosed()) {
            if (cursor.isLast()) {
                cursor.moveToFirst();
            }
            else {
                cursor.moveToNext();
            }
        }   
        }
    
        public void moveToPrevious () {
        if (null != cursor && !cursor.isClosed()) {
            if (cursor.isFirst()) {
                cursor.moveToLast();
            }
            else {
                cursor.moveToPrevious();
            }
        }
        }
    
        public String getCurrentTarget (String targetColumn) throws EmptyCursorException {
        int idx = cursor.getColumnIndex(targetColumn);  
        String value = null;
        try {
            value = cursor.getString(idx);  
        }
        catch (CursorIndexOutOfBoundsException e){
            if ( 0 == cursor.getCount()) {
                throw new EmptyCursorException("Cursor is empty: "+e.getMessage()); 
            }
            else {
                throw e;
            }
        }
        return value;
        }   
    
        public void setCursor (Cursor cursor) {
        this.cursor = cursor;
        }
    
        public void setCursorToNull () {
        this.cursor = null; 
        }
    
        public int getPosition () {
        return this.cursor.getPosition();
        }
    
        public boolean cursorIsClosed () {
        return this.cursor.isClosed();
        }   
    
        public int getCount () {
        return cursor.getCount();
        }
    } // end
    

    Since the adapter is set in onLoadFinished() using the method adapter.setCursor(Cursor), and cleared in onLoaderReset() using the method adapter.setCursorToNull(). Then the adapter has to have these methods.