Как читать базу данных SQLite в Android с помощью курсорного загрузчика?

Я настраиваю свое приложение, чтобы люди могли создавать группы своих друзей. При создании группы она записывает 2 таблицы в базу данных SQL. Первая таблица имеет имя группы и идентификатор группы. Вторая таблица имеет 2 столбца, идентификатор группы и идентификатор пользователя. Это работает нормально.

Однако теперь я хочу иметь возможность читать из базы данных. Я использую фрагмент списка с cursorloader, но у меня возникают проблемы с отображением информации. Я хочу перечислить все имена групп из первой таблицы в моем представлении списка.

Моя проблема в том, что когда я впервые использовал cursorloader для списка своих контактов, я использовал Uri из content provider в методе onCreateLoader. Конкретно у меня был CONTENT_URI из класса ContactsContracts.Contacts.

Пример cursorloader с 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);
}

Однако без использования поставщика контента я не знаю, что вставить в метод onCreateLoader, потому что return new CursorLoader(...) требует Uri во втором аргументе.

Любое предложение о том, как я мог бы отображать данные моей базы данных в виде списка?

код класса фрагмента:

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();
    }
}

}

person Terence Chow    schedule 20.08.2013    source источник
comment
Вы создали контент-провайдера для управления вашей базой данных?   -  person user936414    schedule 20.08.2013
comment
@ user936414 нет, это необходимо? каковы преимущества / недостатки этого? Изменить: просто прочитайте об этом, мне не нужно обмениваться данными между несколькими приложениями, поэтому мне не нужен поставщик контента.   -  person Terence Chow    schedule 20.08.2013


Ответы (2)


Это шаги для создания загрузчика курсора во фрагменте списка.

1) Создайте класс, расширяющий SQLiteOpenHelper, и переопределите onCreate и onUpgrade для создания ваших таблиц.

2) Создайте класс, расширяющий ContentProvider, и создайте URI для доступа к вашей базе данных. См. http://developer.android.com/guide/topics/providers/content-providers.html. Добавьте свои URI в URIMatcher, которые вы используете в onCreate, onUpdate, запросе и т. д. (переопределенные методы), чтобы они соответствовали URI. См. http://developer.android.com/reference/android/content/UriMatcher.html

3) В вызове метода вставки getContext().getContentResolver().notifyChange(uri, null). В методе запроса вызовите setNotificationUri(ContentResolver cr, Uri uri) перед возвратом поставщика контента для внесения изменений, чтобы они автоматически отражались в вашем загрузчике. (https://stackoverflow.com/a/7915117/936414).

4) Укажите этот URI в onCreateLoader.

Примечание. Без поставщика контента автоматическое обновление изменений в списке невозможно в текущей версии Android. Если вы не хотите, чтобы ваш контент-провайдер был виден, установите для атрибута exported в манифесте значение false. Или вы можете реализовать свой пользовательский CursorLoader, как в https://stackoverflow.com/a/7422343/936414 для извлечения данных из базу данных. Но в этом случае автоматическое обновление данных невозможно.

person user936414    schedule 20.08.2013
comment
спасибо за вашу информацию я посмотрю на это. Мне кажется странным, что я могу использовать ContentProvider (который мне в противном случае не нужен) или создать свой собственный CursorLoader (что кажется дополнительной работой)... Не будет ли запрос к базе данных без content provider достаточно распространенным что они сделали бы способ сделать это уже без дополнительной работы? - person Terence Chow; 20.08.2013
comment
Одна из причин использования contentprovider заключается в том, что они могут уведомлять всех зарегистрированных наблюдателей (зарегистрированных в CursorLoader) при изменении контента в базе данных. - person user936414; 20.08.2013
comment
а если это тип данных, который не меняется? Я также нахожу очень странным, что меня заставляют использовать ContentProvider. Нет ли более быстрого/легкого решения для статических данных sqlite? - person Daniele B; 15.11.2013
comment
последняя часть не соответствует действительности: вы можете использовать уведомление Uri даже без поставщика контента. - person njzk2; 24.12.2013
comment
Для уведомления Uri вам нужен URI контента, который является URI, который идентифицирует данные в поставщике (developer.android.com/guide/topics/providers/), что означает внутреннее участие поставщика контента. Также для этого uri следует вызывать notifyChange при каждом изменении базы данных. Только тогда ваш курсор будет уведомлен. - person user936414; 25.12.2013

Android Guide предлагает создать ContentProvider, если вы хотите поделиться своими данными с другими приложениями. Если вам это не нужно, вы можете просто переопределить метод loadInBackgroud() класса CursorLoader. Например, напишите так в своем 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 );
               }
           };
person wholeman    schedule 24.01.2014
comment
Это то, что я использую, потому что мне не нужно делиться своими данными, и я не хочу рефакторить API моей базы данных, чтобы он был ContentProvider. Недостатком является то, что без содержимого ContentProvider мои списки автоматически не узнают, когда данные изменились, что является одной из приятных особенностей ContentProviders. Чтобы обойти это, я воссоздаю свои загрузчики, когда я знаю, что было сделано изменение, то есть вместо вызова Cursor.requery() я теперь использую getLoaderManager().restartLoader(MY_LOADER_ID, null, this);. Это неудобно, но это самый простой способ заменить подход startManagingCursor(), который я нашел. - person Fish; 11.09.2014
comment
@Fish, спасибо за эту информацию, но сейчас мне интересно узнать, насколько дорогим может быть restartLoader()..? Это важно, верно? - person eRaisedToX; 23.04.2017
comment
По какой причине мне не следует возвращать AsyncTaskLoader, если мне не нужно указывать YourProjection, YourSelection, YourSelectionArgs и YourOrder? - person steamer25; 21.06.2017
comment
Ответьте себе, похоже, что CursorLoader управляет CancellationSignal, но не в том случае, если вы переопределяете loadInBackground. Однако довольно легко скопировать эту функциональность в собственный AsyncTaskLoader‹Cursor›. - person steamer25; 21.06.2017
comment
Android Studio уведомила меня The Loader class should be static or leaks might occur... (желтое предупреждение). Кто-нибудь знает, что я могу сделать? Это не ошибка, работает нормально, но все же... - person zeroDivider; 22.12.2017