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.
Related
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.
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.
Say something goes wrong in a Content Provider:
public class MyProvider extends ContentProvider {
// ...
#Override
public Cursor query(...) {
throw new Exception("message");
}
}
and we are using getLoaderManager().initLoader(...) and CursorLoader to use this Content Provider. I understand that Cursor will be null when an exception occurs but how to receive more information about the exception?
public class SomeActivity extends Activity implements
LoaderManager.LoaderCallbacks<Cursor> {
// ...
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if (null != cursor) {
// go on your merry way
} else {
// HERE: find about the Exception and toast a message probably
}
}
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) {
}
}
}
Given I'm developing a simple ListFragment (in this case, it reads a list of Artists from the MediaStore, but will also read data from a different source later) like this:
#EFragment
public class ArtistsFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String TAG = ArtistsFragment.class.getName();
private SimpleCursorAdapter mAdapter;
Uri uri = MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI;
CursorLoader mCursorLoader;
#AfterViews
void setupView() {
mAdapter = new SimpleCursorAdapter(getActivity(),
android.R.layout.simple_list_item_1, null,
new String[]{MediaStore.Audio.Artists.ARTIST}, // lists path of files
new int[]{android.R.id.text1}, 0);
setListAdapter(mAdapter);
getLoaderManager().initLoader(0, null, this);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (mCursorLoader == null) {
mCursorLoader = new CursorLoader(getActivity(), uri, new String[]{MediaStore.Audio.Artists._ID, MediaStore.Audio.Artists.ARTIST},
null, null, MediaStore.Audio.Artists.ARTIST + " ASC");
} else {
System.out.println("mCursorLoader.count: " + mCursorLoader.loadInBackground().getCount());
}
return mCursorLoader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
setListShown(true);
mAdapter.swapCursor(data);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
}
I want to use Robolectric + Mockito + awaitility to proof the Fragment behaves properly on various conditions (e.g. empty list or invalid data etc). My test class looks like this:
#RunWith(RobolectricTestRunner.class)
public class ArtistsFragmentTest {
#Test
public void shouldNotBeNull() {
final ArtistsFragment myFragment = ArtistsFragment_.builder().build();
assertNotNull(myFragment);
// Create a mock cursor.
final Cursor mc = getSampleArtistCursor();
when(mc.getCount()).thenReturn(1);
when(mc.getInt(0)).thenReturn(1);
when(mc.getString(1)).thenReturn("Sample Title");
myFragment.mCursorLoader = mock(CursorLoader.class);
when(myFragment.mCursorLoader.loadInBackground()).thenReturn(mc);
startFragment(myFragment);
assertNotNull(myFragment.getListView());
await().atMost(5, TimeUnit.SECONDS).until(new Callable<Integer>() {
#Override
public Integer call() throws Exception {
return myFragment.getListAdapter().getCount();
}
}, equalTo(1));
System.out.println(myFragment.getListAdapter().getCount());
}
private Cursor getSampleArtistCursor() {
return new CursorWrapper(mock(MockCursor.class));
}
}
Then when running this test in IntelliJ or maven the test will fail, the adapter will always return a count of zero.
The System.out.println statement in onCreateLoader however returns 1. Do I need to take special care for Mockito in background threads? (the loadInBackground method runs on a worker thread).
I've just gotten Loader tests working in my code. In my case I found it more direct to test the loader itself rather than try to route it through the Fragment code.
I wound up using a slightly modified version of the code from this post:
https://groups.google.com/forum/#!msg/robolectric/xY5MF399jA8/V5PnUfh1D-wJ
First, I had to implement some shadow classes because Robolectric2 doesn't include shadow code for the AsyncTaskLoader or Loader classes. If you've never added a shadow class know that it's important you put these in the correct package. Both of these shadows should live in android.support.v4.content.
ShadowLoader
#Implements(Loader.class)
public class ShadowLoader<D> {
// //////////////////////
// Constants
// //////////////////////
// Fields
protected boolean reset;
// //////////////////////
// Constructors
// //////////////////////
// Getter & Setter
// //////////////////////
// Methods from SuperClass/Interfaces
#Implementation
public void reset() {
reset = true;
}
#Implementation
public boolean isReset() {
return reset;
}
// //////////////////////
// Methods
// //////////////////////
// Inner and Anonymous Classes
}
ShadowAsyncTaskLoader
#Implements(AsyncTaskLoader.class)
public class ShadowAsyncTaskLoader<D> extends ShadowLoader {
#RealObject
private AsyncTaskLoader<D> asyncTaskLoader;
#Implementation
void executePendingTask() {
new AsyncTask<Void, Void, D>() {
#Override
protected D doInBackground(Void... params) {
return (D) asyncTaskLoader.loadInBackground();
}
#Override
protected void onPostExecute(D data) {
updateLastLoadCompleteTimeField();
asyncTaskLoader.deliverResult(data);
}
#Override
protected void onCancelled(D data) {
updateLastLoadCompleteTimeField();
asyncTaskLoader.onCanceled(data);
executePendingTask();
}
}.execute((Void)null);
}
public void setReset(boolean reset) {
this.reset = reset;
}
private void updateLastLoadCompleteTimeField() {
try {
Field mLastLoadCompleteTimeField = AsyncTaskLoader.class.getDeclaredField("mLastLoadCompleteTime");
if(!mLastLoadCompleteTimeField.isAccessible()) {
mLastLoadCompleteTimeField.setAccessible(true);
}
mLastLoadCompleteTimeField.set(asyncTaskLoader, SystemClock.uptimeMillis());
} catch(NoSuchFieldException e) {
e.printStackTrace();
} catch(IllegalAccessException e) {
e.printStackTrace();
}
}
}
Then, depending on your configuration you can add an annotation to use the custom classes
#Config( shadows = { ShadowAsyncTaskLoader.class, ShadowLoader.class})
At this point calling loader.onStartLoading() caused the loader to run as expected without having to hack any wait commands into my test cases.
Hope this helps. I haven't tried using the LoaderManager with this method of testing, so I can't verify that it works through that call.
Note: the reason I added ShadowLoader is because I was finding isReset() was returning true when I didn't expect it to.
The solution is to use:
Robolectric.flushForegroundThreadScheduler();
(Robolectric 3.0)
This will run all tasks immediately, including the CursorLoader.