How can I reference a cursor - android

I wish to use a CursorLoader in my application but I don't want to use the returned cursor with a SimpleCursorAdapter. I just want to get a reference of the cursor returned from onLoadFinished()
Here is some code
public class MainActivity extends Activity implements LoaderManager.LoaderCallbacks<Cursor>
{
#Override
public void onCreate(Bundle arg)
{
getLoaderManager().initLoader(0, null, this);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args)
{
return new CursorLoader(getActivity(), baseUri,PROJECTION, select, null, COLNAME );
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data)
{
// rather than swap the cursor with SimpleCursorAdapter reference, I wish to return the cursor so I can reference it
}
}
Any ideas how this can be done

You could just create class member:
private Cursor mCursor;
...
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mCursor = data;
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
mCursor = null;
}
from de docs: onLoaderReset - Called when a previously created loader is being reset, and thus making its data unavailable. The application should at this point remove any references it has to the Loader's data.
And HERE you can see ways to iterate a cursor.

Related

How to run SQLite query asynchronously on background thread?

I have a big database which takes time to find needed information. So I decided to use RxJava to make this process asynchronous.
#Override
public void afterTextChanged(final Editable s) {
final String query = s.toString();
Observable.create(new Observable.OnSubscribe<Cursor>() {
#Override
public void call(Subscriber<? super Cursor> subscriber) {
subscriber.onNext(database.search(query));
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<Cursor>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onNext(Cursor cursor) {
scAdapter.swapCursor(cursor);
}
});
}
But query is running on main thread: EditText where I entering text is freezing.
My question is how to run SQLite query asynchronously on background thread?
Probably this https://developer.android.com/reference/android/app/LoaderManager.html
will suite for you.
Besides, here is short implementation for you, but this is not RxJava.
Firstly, you need to implement LoaderManager.LoaderCallbacks<Cursor>, and usually this interface is implemented by Activity (or Fragment).
In onCreateLoader, a CursorLoader should be created and returned. Here is just an example with MyCursorLoader as descendant of CursorLoader, where you can perform connection to database and queries.
In onLoadFinished you have to treat cursor with results of query.
Please, consider the link to android.com, mentioned above.
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
protected void onResume() {
super.onResume();
// start loading data using LoaderManager of Activity
// third argument only has sense in this case
getLoaderManager().initLoader(0, null, this);
}
private static final String ACTIVITY_NAME = "main_activity";
private void treatCursorRow(Cursor cursor){
// treat record from cursor
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// this callback is called by LoaderManager in order to obtain CursorLoader
// here a new one loader is created
// created loader will be processed by LoaderManager
return new MyCursorLoader(this, ACTIVITY_NAME);
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// this callback is called when loader finishes load cursor
// you don't need to destroy loader - just tread the data
if(data != null)
while(data.moveToNext())
treatCursorRow(data);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
// here you can do something
// relevant to cancelling of loading data
// in example, when you have an event that cancels current
// loading and restarts new one
}
class MyCursorLoader extends CursorLoader {
private static final String DATABASE_NAME = "my_database";
private static final int DATABASE_VERSION = 1;
private String name_param;
public MyCursorLoader(Context context, String activity_name) {
super(context);
name_param = activity_name;
}
#Override
public Cursor loadInBackground() {
// assuming, that we have implemented SQLiteOpenHelper
// to treat sqlite-database
MyDatabaseHelper dbh = new MyDatabaseHelper(
MainActivity.this,
DATABASE_NAME,
null,
DATABASE_VERSION
);
return dbh.getWritableDatabase().rawQuery(
"SELECT * FROM some_table WHERE name=?",
new String[]{ name_param }
);
}
}
}
Another way, is in using of ContentProvider https://developer.android.com/guide/topics/providers/content-providers.html .
In this way you can separate data layer and business logic. Your data access will be abstracted to uris.
Using ContentProvider, you define your queries within it and load data using Uri:
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return getContentResolver().query(
YourContentProvider.SOME_URI,
null,
null,
null,
null
);
}
This is convenient way if you have more than one or two customers of your data (Activities or Fragments) - you will use just predefined uris rather repeating sql queries or creating many CursorLoaders descendands.
Moreover, ContentProvider may be used from outside your app if you want.

SearchView implementation via CursorAdapter and CursorLoader. swapCursor is giving AStaleDataException

I'm trying to implement SearchView in a Fragment, with search results queried for directly from sqlite via CursorLoader and search results rendered in the same Fragment via custom CursorAdapter. Search suggestions are also queried for directly from sqlite and rendered via custom CursorAdapter (CountrySuggestionsAdaptor).
So, I have one CursorLoader for getting suggestions(loader id =0) and the other CursorLoader for getting search results(loader id=1). There are 3 problems with loading suggestions at the moment:
1) Typing the first letter doesn't show any suggestions, i.e. it doesn't call bindView of the custom CursorAdapter. It only starts showing suggestions after second typing
2)If I type "Un" it will give suggestions, then if I type "Ur" it will give this error
android.database.StaleDataException: Attempting to access a closed
CursorWindow.Most probable cause: cursor is deactivated prior to
calling this method.
at android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:139)
at android.database.AbstractWindowedCursor.getLong(AbstractWindowedCursor.java:74)
at android.support.v4.widget.CursorAdapter.getItemId(CursorAdapter.java:226)
at android.widget.AutoCompleteTextView.buildImeCompletions(AutoCompleteTextView.java:1132)
at android.widget.AutoCompleteTextView.showDropDown(AutoCompleteTextView.java:1091)
at android.widget.AutoCompleteTextView.updateDropDownForFilter(AutoCompleteTextView.java:974)
at android.widget.AutoCompleteTextView.onFilterComplete(AutoCompleteTextView.java:956)
at android.widget.Filter$ResultsHandler.handleMessage(Filter.java:285)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5294)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:699)
3) First I type "Un" it gives suggestions, I click on the first suggestion and it successfully renders me a list based on it. Then I additionally type the third letter into search view, such as "Uni" and it crashes giving me this letter:
java.lang.IllegalStateException: attempt to re-open an already-closed
object: SQLiteQuery: Select _id,countryNameRu, countryId FROM
countries WHERE countryNameRu LIKE 'Un%'
at android.database.sqlite.SQLiteClosable.acquireReference(SQLiteClosable.java:55)
at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:58)
at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:152)
at android.database.sqlite.SQLiteCursor.onMove(SQLiteCursor.java:124)
at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:214)
at android.support.v4.widget.CursorAdapter.getItemId(CursorAdapter.java:225)
at android.widget.AdapterView.rememberSyncState(AdapterView.java:1226)
at android.widget.AdapterView$AdapterDataSetObserver.onChanged(AdapterView.java:820)
at android.widget.AbsListView$AdapterDataSetObserver.onChanged(AbsListView.java:6156)
at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37)
at android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50)
at android.support.v4.widget.CursorAdapter.swapCursor(CursorAdapter.java:347)
at android.support.v4.widget.CursorAdapter.changeCursor(CursorAdapter.java:315)
at android.support.v4.widget.CursorFilter.publishResults(CursorFilter.java:68)
at android.widget.Filter$ResultsHandler.handleMessage(Filter.java:282)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5294)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:699)
I've been looking for solutions all over the internet for several days now, I'm kinda desperate at the moment. I thought about implementing ContentProvider, but how would it help and is it absolutely necessary?
The fragment:
import android.database.Cursor;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.widget.SearchView;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Callback;
public class RoamingTariffs extends ProgressListFragment implements LoaderManager.LoaderCallbacks<Cursor>{
private RoamingTariffSearchResultsAdapter roamingTariffAdapter;
private CountrySuggestionsAdaptor mSearchViewAdapter;
RoamingTariffDbHelper dbHelper;
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.roaming_toolbar, menu);
mSearchViewAdapter = new CountriesSearchResultsAdaptor(this.getActivity(),null);
SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
searchView.setSuggestionsAdapter(mSearchViewAdapter);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener(){
#Override
public boolean onQueryTextSubmit(String s) {
if(!s.isEmpty())
loadSuggestions(s,false);
return true;
}
#Override
public boolean onQueryTextChange(String s) {
if(!s.isEmpty())
loadSuggestions(s,false);
return true;
}
});
searchView.setOnSuggestionListener(
new SearchView.OnSuggestionListener(){
#Override
public boolean onSuggestionSelect(int position) {
Cursor cursor = (Cursor) mSearchViewAdapter.getItem(position);
String countryId1 = cursor.getString(1);
loadCountryDetails(countryId1);
return true;
}
#Override
public boolean onSuggestionClick(int position) {
Cursor cursor = (Cursor) mSearchViewAdapter.getItem(position);
String countryId1 = cursor.getString(1);
loadCountryDetails(countryId1);
return false;
}
}
);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
roamingTariffAdapter = new RoamingTariffCursorAdapter(getActivity(), null);
getListView().setAdapter(roamingTariffAdapter);
dbHelper = new RoamingTariffDbHelper(getActivity());
((LoginActivity) getActivity()).getSupportActionBar().setTitle("Roaming Tariffs");
RoamingTariffInterface roamingTariffService =
RoamingTariffClient.getClient().create(RoamingTariffInterface.class);
Call<List<CountryDto>> call = roamingTariffService.getCountries(countryId);
call.enqueue(new Callback<List<CountryDto>>() {
#Override
public void onResponse(Call<List<CountryDto>> call, Response<List<CountryDto>> response) {
List<CountryDto> countryList = response.body();
if (countryList == null)
countryList = new ArrayList<CountryDto>();
// CREATE SQLITE TABLE AND SAVE
RoamingTariffDbHelper helper = new RoamingTariffDbHelper(getActivity());
for (CountryDto countryDto : countryList) {
helper.addCountryEntry(countryDto);
}
}
#Override
public void onFailure(Call<List<CountryDto>> call, Throwable t) {
showError();
}
});
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
showProgress();
CursorLoader cursorLoader = null;
switch (id){
case 0://loadSuggestions
final String queryText = args.getString("queryText");
cursorLoader = new CursorLoader(getActivity()) {
#Override
public Cursor loadInBackground() {
Cursor cursor = dbHelper.getCountryNamesBySearchLetters(queryText);
return cursor;
}
};
break;
case 1://load results
final String countryId = args.getString("countryId");
cursorLoader = new CursorLoader(getActivity()) {
#Override
public Cursor loadInBackground() {
Cursor cursor = dbHelper.getOperatorsByCountryId(countryId);
return cursor;
}
};
break;
}
return cursorLoader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
int loaderId = loader.getId();
switch(loaderId){
case 0://suggestions are ready to show
if (data != null)
mSearchViewAdapter.swapCursor(data);
break;
case 1://search results are ready to show
if(data !=null){
roamingTariffAdapter.swapCursor(data);}
break;
}
showContent();
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
mSearchViewAdapter.swapCursor(null);
roamingTariffAdapter.swapCursor(null);
}
public void loadSuggestions(String query){
Bundle bundle = new Bundle();
bundle.putString("queryText", query);
getLoaderManager().restartLoader(0, bundle, this);
}
public void loadCountryDetails(String countryId){
Bundle bundle = new Bundle();
bundle.putString("countryId", countryId);
getLoaderManager().restartLoader(1, bundle, this);
}
...
}
The suggestion adapter:
public class CountrySuggestionsAdaptor extends CursorAdapter {
private Context context = null;
public CountrySuggestionsAdaptor(Context context, Cursor c) {
super(context, c, 0);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.country_suggestion_item,parent, false);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
TextView countryView = (TextView)view.findViewById(R.id.country_item);
countryView.setText(cursor.getString(1));
}
}
The relevant query method from RoamingTariffDbHelper:
public Cursor getCountryNamesBySearchLetters(String startingStr){
SQLiteDatabase db = this.getReadableDatabase();
String query = RoamingTariffDbContract.SQL_SELECT_COUNTRIES_START_WITH+
Helper.firstLetterToCapital(startingStr)+"%'";
Cursor cursor = db.rawQuery(query,null);
if(cursor == null){
return null;
}
else if(!cursor.moveToFirst()) {
cursor.close();
return null;
}
return cursor;
}
Edit: I followed #pskink's advice and removed querytextlisteners and added this instead
FilterQueryProvider fqp =new FilterQueryProvider() {
#Override
public Cursor runQuery(CharSequence constraint) {
Cursor cursor = null;
if(constraint.length()!=0)
cursor = dbHelper.getCountryNamesBySearchLetters(constraint.toString());
return cursor;
}
};
Problems 2 and 3 are gone. Problem number 1 persists. I've debugged inside runQuery, on the first type constraint is null for some reason, hence no suggestions. Although constraint should be the first letter I've typed. What's the reason?

Notify AsyncTaskLoader when data source is changed from a thread

I'm new in android and I'm not sure if what I'm looking for possible, is it possible to update the listview content handled by the loader manager from a thread? if yes can you please show me how?
More details are below, I've removed many lines for brevity, please let me know if you need more details
The HandlerThread I'm using is this and where I need to notify the loader about the change to udpate the listview content
public void syncWithBackend(Context context) {
//Connect to the server over HTTP and get the latest data after receiving
//the GCM tickle message then save the result in DB
dbhelper.saveFm(id, artno, comment );
//Here is where I need to notify the loader or the list about the new change
// to view the new saved data
context.getContentResolver().notifyChange(uri, null);
}
The URI I'm using is belwo,
Uri uri = Uri.parse("sqlite://com.pack.android.and/posts");
my LoaderCallbacks is this
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new FeedListCursorLoader(getActivity());
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
cCursorAdapter adapter = new cCursorAdapter(getActivity(),
(fCursor) cursor);
setListAdapter(adapter);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
setListAdapter(null);
}
My Intent Service is this
protected void onHandleIntent(Intent intent) {
//Here I got the GCM message, call the thread to sync with the backend
hanlder1.queueHttp(this);
}
My AsyncTaskLoader is this
public abstract class SQLiteCursorLoader extends AsyncTaskLoader<Cursor> {
private Cursor cCursor;
public SQLiteCursorLoader(Context context) {
super(context);
}
//I'm overriding this from the Fragment to get the cursor after querying sqlite DB
protected abstract Cursor loadCursor();
#Override
public Cursor loadInBackground() {
Cursor cursor = loadCursor();
if (cursor != null) {
cursor.getCount();
cursor.setNotificationUri(getContext().getContentResolver(), uri);
}
return cursor;
}
#Override
public void deliverResult(Cursor data) {
Cursor oldCursor = cCursor;
cCursor = data;
if (isStarted()) {
super.deliverResult(data);
}
if (oldCursor != null && oldCursor != data && !oldCursor.isClosed()) {
oldCursor.close();
}
}
#Override
protected void onStartLoading() {
...
}
#Override
protected void onStopLoading() {
...
}
#Override
public void onCanceled(Cursor cursor) {
...
}
#Override
protected void onReset() {
...
}
Yes, but not quite as you expect.
You will not update the cursor, you will replace it. In fact, the LoaderManager will do it for you, automatically. It is almost unbelievably simple
When you create a cursor, in response to the request from your Loader, register it as a listener for a particular URI:
cursor.setNotificationUri(getContext().getContentResolver(), uri);
The URI can be anything you want. It represents (is the "name" of?) the data.
When your Loader is run by the LoaderManager, the LoaderManager gets the cursor that the Loader returns, before it hands it to you, in the callback. It registers as a listener, on that cursor.
If, at some point after that, you announce that a change has taken place, in the data that that URI represents, like this:
getContext().getContentResolver().notifyChange(uri, null);
The cursor (registered as a listener on that URI) will be notified. When the cursor is notified, the LoaderManager (which registered as a listener on the cursor) will be notified, and it will, automatically, re-run the Loader!
In your case, you would do the notification on that other thread...
If your onLoadFinished method simply swaps out the old cursor and swaps in the new one, your ListView will update by magic.
Some parts of Android are just awesome.
Edited to add sample code:
Here is some example code to demonstrate how this works. db is a reference to a SQLiteOpenHelper. The methods insert and query are simple db insert and query respectively.
First the insert code:
db.insert();
getContentResolver().notifyChange(DbHelper.URI, null);
Then the Loader:
private static class MagicLoader extends AsyncTaskLoader<Cursor> {
private final DbHelper db;
private volatile Cursor cursor;
private final ContentObserver obs = new ContentObserver(new Handler()) {
#Override public boolean deliverSelfNotifications() { return true; }
#Override public void onChange(boolean selfChange) { onContentChanged(); }
};
public MagicLoader(Context ctxt, DbHelper db) {
super(ctxt);
this.db = db;
}
#Override
protected void onStartLoading() {
if (null == cursor) { forceLoad(); }
else { deliverResult(cursor); }
}
#Override
public Cursor loadInBackground() {
cursor = db.query();
if (cursor != null) {
try {
cursor.setNotificationUri(
getContext().getContentResolver(),
DbHelper.URI);
cursor.getCount();
cursor.registerContentObserver(obs);
}
catch (RuntimeException ex) {
cursor.close();
throw ex;
}
}
return cursor;
}
};
Note that this would all be a lot easier if you were just using a ContentProvider.

Set multiple cursor loaders with multiple adapters - Android

I want to have two cursor loaders in my application fragment. Each one of them has different set of data and is used in different lists.
I found somewhere that with cursor you can use getId() method and then using switch do something. But, there is always method: getLoaderManager().initLoader(0,null,this); after which can be something like this:
adapter = new SimpleCursorAdapter(context, android.R.layout.simple_list_item_1, null, from, to, 0);
This method is only for one cursor, but what if I have adapter1 and adapter2? How can I determine which cursor is for which adapter?
Having separate loader callbacks is not necessary. The answer lies in the ID passed to the loader manager:
private static final int LOADER_A_ID = 1;
private static final int LOADER_B_ID = 2;
public void onCreate(Bundle savedInstanceState) {
...
getLoaderManager().initLoader(LOADER_A_ID, null, this);
getLoaderManager().initLoader(LOADER_B_ID, null, this);
}
public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
switch (loaderId) {
case LOADER_A_ID:
// return loader for cursor A/ adapter A
case LOADER_B_ID:
// return loader for cursor B/ adapter B
}
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
switch (loader.getId()) {
case LOADER_A_ID:
adapterA.swapCursor(cursor);
break;
case LOADER_B_ID:
adapterB.swapCursor(cursor);
break;
}
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
...
}
You can make as many cursor loaders as you like in this way and only implement the callback methods once.
My solution is to implement callback listeners
public class Query extends FragmentActivity {//do not implement callbacks
getSupportLoaderManager().initLoader(Constants.LOADER_DAILY_TASK,
null,dailyTaskResultLoaderListener);//note listener not "this"
getSupportLoaderManager().initLoader(Constants.LOADER_OTHER_TASK,
null,otherTaskResultLoaderListener);//note listener not "this"
private LoaderCallbacks<Cursor> dailyTaskResultLoaderListener
= new LoaderCallbacks<Cursor>() {
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle bun) {
return new CursorLoader(getBaseContext(),
Constants.DAILY_TASK_URI,projection, selection,selArgs,null); }
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor)
{...}
public void onLoaderReset(Loader<Cursor> cursor) {...}};
private LoaderCallbacks<Cursor> otherTaskResultLoaderListener
= new LoaderCallbacks<Cursor>() {
....I think you get the idea
One possible solution is this. Remove the LoaderCallbacks interface from your Fragment declaration, then create two separate implementations of LoaderCallbacks, one for each adapter that you want to set up. Finally in the onLoadFinished method of each implementation set up the adapters.
public class ExampleFragmen extends Fragment { // Don't implement LoaderCallbacks here.
private static final int LOADER_A = 0;
private static final int LOADER_B = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
getLoaderManager().restartLoader(LOADER_A, null, new LoaderACallbacks());
getLoaderManager().restartLoader(LOADER_B, null, new LoaderBCallbacks());
...
}
public class LoaderACallbacks implements LoaderCallbacks<Cursor> {
#Override
public Loader<Cursor> onCreateLoader(int loader, Bundle args) {
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// Set up adapter A here...
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
}
}
public class LoaderBCallbacks implements LoaderCallbacks<Cursor> {
#Override
public Loader<Cursor> onCreateLoader(int loader, Bundle args) {
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// Set up adapter B here...
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
}
}
}

How to read an SQLite DB in android with a cursorloader?

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

Categories

Resources