How to run SQLite query asynchronously on background thread? - android

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.

Related

What happens when you modify class attributes in AsyncTaskLoader

When you try to modify/read a class attribute from the loadInBackground() method. What happens? Does android make a deep copy before passing in the variable?
Do modifications inside loadInBackground() actually change the class attribute values on the exterior context?
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Void>
{
public List<String> some_array = new ArrayList();
public String some_string = "Hello world";
...
#Override
public Loader<Void> onCreateLoader(int id, Bundle args)
{
return new AsyncTaskLoader<Void>(MainActivity.this)
{
#Override
protected void onStartLoading()
{
forceLoad();
}
#Override
public Void loadInBackground()
{
some_array.add("some element");
some_string = "good bye";
}
};
}
#Override
public void onLoadFinished(Loader<Void> loader, Void data)
{
// what are the values of some_array and some_string now?
}
#Override
public void onLoaderReset(Loader<Void> loader)
{
}
}
Okay so I manually tested it and found that in Java, arrays are passed as pointers to the background thread.
Therefore, modifications in the background thread do alter the class variables, but this should be avoided as it is asynchronous and can quickly become unpredictable and chaotic.
My current approach is to store the changes in a temporary, overwritable array and then merge them back in the onLoadFinished method, when we are all back in the same thread.

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.

AsyncTaskLoader basic example. (Android)

I am using a Loader in my application and based on the result I get from the query I perform on COntacts using this Loader I perform some calculations and store them back in a Sqlite DB. I want this operation to be Asynchronous, however I am confused between using an Async task, as I have lot of different data types to return or should I use a simple handler or an AsyncTaskLoader, I want it to be simple as I am new to Loaders. I tried to search around for examples of AsyncTaskLoader but it seems rocket science, a basic and simple functional example of any of the three in the context of my scenario would be a lot helpful.
If you wish to use AsyncTaskLoader, here's a nice sample for you.
EDIT: I've decided to make a simpler solution (based on this repo):
public abstract class AsyncTaskLoaderEx<T> extends AsyncTaskLoader<T> {
private static final AtomicInteger sCurrentUniqueId = new AtomicInteger(0);
private T mData;
public boolean hasResult = false;
public static int getNewUniqueLoaderId() {
return sCurrentUniqueId.getAndIncrement();
}
public AsyncTaskLoaderEx(final Context context) {
super(context);
onContentChanged();
}
#Override
protected void onStartLoading() {
if (takeContentChanged())
forceLoad();
//this part should be removed from support library 27.1.0 :
//else if (hasResult)
// deliverResult(mData);
}
#Override
public void deliverResult(final T data) {
mData = data;
hasResult = true;
super.deliverResult(data);
}
#Override
protected void onReset() {
super.onReset();
onStopLoading();
if (hasResult) {
onReleaseResources(mData);
mData = null;
hasResult = false;
}
}
protected void onReleaseResources(T data) {
//nothing to do.
}
public T getResult() {
return mData;
}
}
Usage:
in your activity:
getSupportLoaderManager().initLoader(TASK_ID, TASK_BUNDLE, new LoaderManager.LoaderCallbacks<Bitmap>() {
#Override
public Loader<Bitmap> onCreateLoader(final int id, final Bundle args) {
return new ImageLoadingTask(MainActivity.this);
}
#Override
public void onLoadFinished(final Loader<Bitmap> loader, final Bitmap result) {
if (result == null)
return;
//TODO use result
}
#Override
public void onLoaderReset(final Loader<Bitmap> loader) {
}
});
inner static class , or a normal class:
private static class ImageLoadingTask extends AsyncTaskLoaderEx<Bitmap> {
public ImageLoadingTask (Context context) {
super(context);
}
#Override
public Bitmap loadInBackground() {
//TODO load and return bitmap
}
}
Update: starting from support library 27.1.0, things changed a bit (link here) :
In version 27.1.0, onStartLoading() is called every time the Activity
is started. Since you call deliverResult() in onStartLoading(), you
trigger onLoadFinished(). This is Working as Intended.
You should remove your call to deliverResult() from onStartLoading()
as it is not needed (Loaders already deliver results computed in
loadInBackground() without any additional work needed on your part).
I've updated the code above for this change.
EDIT:
Updated, kotlin version can be found here.
Since Honeycomb and the v4 Compatibility Library it is possible to use AsyncTaskLoader. From what I understand, the AsyncTaskLoader can survive through config changes like screen flips. But using AsyncTask you can mess up with configuration changes.
Key information: AsyncTaskLoader is subclass of Loader. This class performs the same function as the AsyncTask, but a bit better, it can also be useful in handling configuration changes (screen orientation).
A very good example and explanation is given here.
http://www.javacodegeeks.com/2013/01/android-loaders-versus-asynctask.html
Google has a pretty good example directly in the API Docs.
Android Design Patterns provides some more detail and the reasoning behind Loaders.
This tutorial will definetly help You. http://www.javacodegeeks.com/2013/08/android-custom-loader-to-load-data-directly-from-sqlite-database.html
Here's step by step tutorial to implement AsyncTaskLoader. or check out this same article on Medium
Implement LoaderManager.LoaderCallbacks<String> on MainActivity and create a static int to uniquely identify your loader and create a String key to pass string url to your loader
public class MainActivity extends AppCompatActivity
implements LoaderManager.LoaderCallbacks<String>{
public static final int OPERATION_SEARCH_LOADER = 22;
public static final String OPERATION_QUERY_URL_EXTRA = "query";
//...}
Override onCreateLoader,onLoadFinishedand onLoaderReset functions inside MainActivity
#Override
public Loader<String> onCreateLoader(int id, final Bundle args) {
//Here we will initiate AsyncTaskLoader
return null;
}
#Override
public void onLoadFinished(Loader<String> loader, String operationResult) {
//Think of this as AsyncTask onPostExecute method, the result from onCreateLoader will be available in operationResult variable and here you can update UI with the data fetched.
Log.d("MAINACTIVITY","result : "+ operationResult);
}
#Override
public void onLoaderReset(Loader<String> loader) {
//Don't bother about it, Android Studio will override it for you
}
inside onCreateLoader() return a new AsyncTaskLoader<String> as an anonymous inner class with this as the constructor's parameter and override loadInBackground & onStartLoading inside anonymous
inner class
#Override
public Loader<String> onCreateLoader(int id, final Bundle args) {
return new AsyncTaskLoader<String>(this) {
#Override
public String loadInBackground() {
//Think of this as AsyncTask doInBackground() method, here you will actually initiate Network call
return null;
}
#Override
protected void onStartLoading() {
//Think of this as AsyncTask onPreExecute() method,start your progress bar,and at the end call forceLoad();
forceLoad();
}
};
}
Inside loadInBackground make a network call using HTTPUrlConnection or OKHttp or anything that you use.
#Override
public String loadInBackground() {
String url = args.getString(OPERATION_QUERY_URL_EXTRA);//This is a url in string form
if (url!=null&&"".equals(url)) {
return null;//if url is null, return
}
String operationResult="";
try {
operationResult = NetworkUtils.getResponseFromHttpUrl(url);//This just create a HTTPUrlConnection and return result in strings
} catch (IOException e) {
e.printStackTrace();
}
return operationResult;
}
Inside onCreate initialize the loader with OPERATION_SEARCH_LOADER as the ID, null for the bundle, and this for the context
getSupportLoaderManager().initLoader(OPERATION_SEARCH_LOADER, null, this);
Now call this method, whenever and wherever you want to trigger the loader
private void makeOperationSearchQuery(String url) {
// Create a bundle called queryBundle
Bundle queryBundle = new Bundle();
// Use putString with OPERATION_QUERY_URL_EXTRA as the key and the String value of the URL as the value
queryBundle.putString(OPERATION_QUERY_URL_EXTRA,url);
// Call getSupportLoaderManager and store it in a LoaderManager variable
LoaderManager loaderManager = getSupportLoaderManager();
// Get our Loader by calling getLoader and passing the ID we specified
Loader<String> loader = loaderManager.getLoader(OPERATION_SEARCH_LOADER);
// If the Loader was null, initialize it. Else, restart it.
if(loader==null){
loaderManager.initLoader(OPERATION_SEARCH_LOADER, queryBundle, this);
}else{
loaderManager.restartLoader(OPERATION_SEARCH_LOADER, queryBundle, this);
}
}
Walla, you are done, just to remind you NetworkUtils.getResponseFromHttpUrl(url); is my custom function which take string convert it into URL which in turn used to create HTTPUrlConnection
I like this brief example AsyncTask and AsyncTaskLoader.
class FooLoader extends AsyncTaskLoader {
public FooLoader(Context context, Bundle args) {
super(context);
// do some initializations here
}
public String loadInBackground() {
String result = "";
// ...
// do long running tasks here
// ...
return result;
}
}
class FooLoaderClient implements LoaderManager.LoaderCallbacks {
Activity context;
// to be used for support library:
// FragmentActivity context2;
public Loader onCreateLoader(int id, Bundle args) {
// init loader depending on id
return new FooLoader(context, args);
}
public void onLoadFinished(Loader loader, String data) {
// ...
// update UI here
//
}
public void onLoaderReset(Loader loader) {
// ...
}
public void useLoader() {
Bundle args = new Bundle();
// ...
// fill in args
// ...
Loader loader =
context.getLoaderManager().initLoader(0, args, this);
// with support library:
// Loader loader =
// context2.getSupportLoaderManager().initLoader(0, args, this);
// call forceLoad() to start processing
loader.forceLoad();
}
}
Simplifying hard, maybe
private void loadContent() {
getLoaderManager().initLoader(1000, new Bundle(),
new LoaderManager.LoaderCallbacks<List<String>>() {
#Override
public Loader<List<String>> onCreateLoader(int id, Bundle args) {
return new AsyncTaskLoader<List<String>>(MainActivity.this.getApplicationContext()) {
#Override
public List<String> loadInBackground() {
Log.i("B", "Load background data ");
ArrayList<String> data = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
data.add("Data." + i + " " + System.currentTimeMillis());
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return data;
}
};
}
#Override
public void onLoadFinished(Loader<List<String>> loader, List<String> data) {
Log.i("B", "Here are your data loaded" + data);
if (!loader.isAbandoned()) {
mAdapter.setData(data); // Read also about RecyclerView
}
}
#Override
public void onLoaderReset(Loader<List<String>> loader) {
Log.i("B", "Loader reset");
}
}).forceLoad();
}
#Override
protected void onDestroy() {
// Abandon the loader so that it should not attempt to modify already dead GUI component
getLoaderManager().getLoader(1000).abandon();
super.onDestroy();
}
Make this part of your Activity. The sample simulates delay, but makes new entries easy to recognize because they will have the different time stamp suffix. Of course you also need RecyclerView to display the data, the answer to this question seems very good.
The loader in this example is the inner class that keeps the reference to the parent activity. It must be external static class without such reference in production.
I prefer using Bolts-Android. it is very easy.
https://github.com/BoltsFramework/Bolts-Android
Task.callInBackground(new Callable<Void>() {
public Void call() {
// Do a bunch of stuff.
}
}).continueWith(...);

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

Testing a CursorLoader with Robolectric & Mockito

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.

Categories

Resources