34

I'm setting up my app so that people can create groups of their friends. When a group is created, it writes 2 tables to the SQL database. The first table has a group name and a group id. The second table has 2 columns, a group id and a user id. This is working fine.

However, now I want to be able to read from the database. I'm using a listview fragment with a cursorloader but I'm having trouble getting the information to display. I want to list all the group names from the first table in my list view.

My problem is that, when I first used the cursorloader to list my contacts, I was using a Uri from the content provider in the onCreateLoader method. Specifically I had CONTENT_URI from the ContactsContracts.Contacts class.

Example of cursorloader with contentprovider:

@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
    Uri contentUri = ContactsContract.Contacts.CONTENT_URI;
    return new CursorLoader(getActivity(),contentUri,PROJECTION,SELECTION,ARGS,ORDER);
}

However, without using a content provider, I don't know what to put in the onCreateLoader method because return new CursorLoader(...) requires a Uri in the second argument.

Any suggestion on how I might be able to display my database data in a listview?

fragment class code:

public class GroupListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> {

CursorAdapter mAdapter;
private OnItemSelectedListener listener;
private static final String[] PROJECTION ={GroupContract.GroupDetails.COLUMN_NAME_GROUP_NAME};
private static final String SELECTION = null;
final String[] FROM = {GroupContract.GroupDetails.COLUMN_NAME_GROUP_NAME};
final int[] TO = {android.R.id.text1};
private static final String[] ARGS = null;
private static final String ORDER = null;
private Cursor c;


@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_1,null,FROM,TO,0 );
    ReadDBAsync readDB = new ReadDBAsync();
    readDB.execute();
}

@Override
public void onActivityCreated(Bundle savedInstanceState){
    super.onActivityCreated(savedInstanceState);
    setListAdapter(mAdapter);
    getLoaderManager().initLoader(0,null,this);
}

@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
    Uri contenturi = Uri.parse("content://preamble.oneapp");
    Uri tableuri = Uri.withAppendedPath(contenturi,GroupContract.GroupDetails.TABLE_NAME);
    return new CursorLoader(getActivity(),tableuri,PROJECTION,SELECTION,ARGS,ORDER);
}

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

@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
    mAdapter.swapCursor(null);
}


private class ReadDBAsync extends AsyncTask<Void,Void,String> {

    @Override
    protected String doInBackground(Void... voids) {

        ContractDBHelpers mDBHelper = new ContractDBHelpers(getActivity());
        SQLiteDatabase db = mDBHelper.getReadableDatabase();
        String returnvalue = "database read";
        c = db.query(GroupContract.GroupDetails.TABLE_NAME,PROJECTION,null,null,null,null,null);
        return returnvalue;
    }

    @Override
    protected void onPostExecute(String result){
        Toast.makeText(getActivity(), result, Toast.LENGTH_LONG).show();
    }
}

}
Terence Chow
  • 10,755
  • 24
  • 78
  • 141
  • Have you created a content provider to manage your database ? – user936414 Aug 20 '13 at 04:30
  • @user936414 no, is that necessary? what are the benefits / disadvantages of doing so? Edit: just read up on it, i don't need to share the data across multiple applications so I don't need a content provider – Terence Chow Aug 20 '13 at 04:38

2 Answers2

65

Android Guide suggests to create a ContentProvider when you want to share your data with other applications. If you don't need this, you can just override method loadInBackgroud() of the CursorLoader class. For example write like this in your onCreateLoader:

return new CursorLoader( YourContext, null, YourProjection, YourSelection, YourSelectionArgs, YourOrder )
           {
               @Override
               public Cursor loadInBackground()
               {
                   // You better know how to get your database.
                   SQLiteDatabase DB = getReadableDatabase();
                   // You can use any query that returns a cursor.
                   return DB.query( YourTableName, getProjection(), getSelection(), getSelectionArgs(), null, null, getSortOrder(), null );
               }
           };
eRaisedToX
  • 3,221
  • 2
  • 22
  • 28
wholeman
  • 751
  • 5
  • 7
  • 12
    This is what I use because I don't need to share my data, and I don't want to refactor my database API to be a ContentProvider. The downside is that without the ContentProvider stuff my listviews don't automatically know when the data have changed, which is one of the nice things about ContentProviders. To get around that I recreate my loaders when I know a change has been made, i.e. instead of calling `Cursor.requery()` I now use `getLoaderManager().restartLoader(MY_LOADER_ID, null, this);`. This is awkward, but it's the simplest way I've found to replace the `startManagingCursor()` approach. – Fish Sep 11 '14 at 15:31
  • @Fish , Thanks for this info, but what I am interested in knowing now is **how expensive `restartLoader()` can be..?** That matters right? – eRaisedToX Apr 23 '17 at 06:11
  • Any reason I shouldn't return an AsyncTaskLoader if I don't care to specify YourProjection, YourSelection, YourSelectionArgs and YourOrder? – steamer25 Jun 21 '17 at 17:59
  • Reply to self, it looks like CursorLoader manages a CancellationSignal, but not if you override loadInBackground. It's pretty easy to copy that functionality into a custom AsyncTaskLoader, though. – steamer25 Jun 21 '17 at 18:09
  • 1
    Android Studio notified me `The Loader class should be static or leaks might occur...` (yellow warning). Does anyone know what could I do? It is not the error, works fine, but still... – zeroDivider Dec 22 '17 at 08:40
35

These are the steps to create a cursorloader in a list fragment

1) Create a class extending SQLiteOpenHelper and override onCreate and onUpgrade to create your tables.

2) Create a class extending ContentProvider and create the URIs to access your database. Refer http://developer.android.com/guide/topics/providers/content-providers.html. Add your URIs to the URIMatcher which you use in onCreate, onUpdate, query, etc (overridden methods) to match the URI. Refer http://developer.android.com/reference/android/content/UriMatcher.html

3) In the insert method call getContext().getContentResolver().notifyChange(uri, null). In the query method call setNotificationUri(ContentResolver cr, Uri uri) before returning the content provider for the insertion change to reflect automatically to your loader. (https://stackoverflow.com/a/7915117/936414).

4) Give that URI in onCreateLoader.

Note: Without a content provider, automatic refreshing of changes to the list is not feasible as of the current android version. If you don't want to have your contentprovider visible, set exported attribute in manifest to false. Or you can implement your custom CursorLoader as in https://stackoverflow.com/a/7422343/936414 to retrieve data from the database. But in this case automatic refreshing of data is not possible

Community
  • 1
  • 1
user936414
  • 7,574
  • 3
  • 30
  • 29
  • 9
    thanks for your information I'll look into it. I find it odd that my options are to use a `ContentProvider` (which I don't otherwise need) or create my own custom `CursorLoader` (which seems to be additional work)...Wouldn't querying the database without a `content provider` be common enough that they would have made a way to do this already without doing additional work? – Terence Chow Aug 20 '13 at 05:08
  • One of the reasons for having contentprovider is that they can notify all the registered Observers(registered in CursorLoader) when the content in the database changes. – user936414 Aug 20 '13 at 05:21
  • 5
    what about if this a type of data which doesn't change? I also find it very odd to be forced to use a `ContentProvider`. Isn't there a quicker/lighter solution for static sqlite data? – Daniele B Nov 14 '13 at 23:34
  • the last part is not true: you can use the notificationUri even without a contentprovider. – njzk2 Dec 24 '13 at 18:48
  • 1
    For notificationUri, you need a content URI which is a URI which identifies data in a provider(http://developer.android.com/guide/topics/providers/content-provider-basics.html) which means a content provider is involved internally. Also notifyChange should be called on that uri whenever there is a change to the database. Only then your cursor will be notified. – user936414 Dec 25 '13 at 01:33