0

I am noticing some IllegalStateException crashes in my analytics report that I can't seem to reproduce. Am I doing something structurally incorrect here? I have included some pseudocode that hopefully shows my fragment's skeleton without being too cluttered. I don't recall seeing this error until I refactored my code to use fragments. The error occurs in AsyncTask's onPostExecute. Please let me know if I can better clarify my problem / pseudocode better.

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

    Fragment fragment = this;
    SimpleCursorAdapter adapter;

    // onActivityCreated initializes the adapter and the loader

    public AsyncTaskLoader<Cursor> onCreateloader(int id, Bundle bundle) {
        return new CustomCursorLoader(this);
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        adapter.swapCursor(data);
    }

    public void onLoaderReset(Loader<Cursor> loader) {
        adapter.swapCursor(null);
    }

    private class CustomCursorLoader extends CursorLoader {
        public CustomCursorLoader(Fragment fragment) {
            // get a handle to the OrmLite database helper class
            databaseHelper = (DatabaseHelper) OpenHelperManager.getHelper(fragment.getActivity(), DatabaseHelper.class);
        }

        public Cursor loadInBackground() {
            // construct the Cursor that will be used for loading results from the SQLite database
        }
    }

    private class Task extends AsyncTask {
        // pre execute

        // grab some new database results from the server and repopulate the SQLite database using OrmLite

        public void onPostExecute() {
            fragment.getLoaderManager().restartLoader(LOADER_ID, null, fragment);
        }
    }
}

Update

I was able to reproduce the issue by initiating the Task and then immediately hitting back. When the Task finishes the activity that contains the fragment is not active so the call to getLoaderManager fails. The question now is, what is the most elegant way to handle this? I suppose I can just catch the Exception.

Update 2

I believe I can make use of cancel to prevent onPostExecute from executing. If I do this I will probably need to move initLoader to another method than onActivityCreated in order to refresh the loader when the user does come back to the page (since the results updated, just not the loader).

Update 3

I am wondering, like Robby Pond mentions in the comments, if using a Loader here is overkill?

theblang
  • 10,215
  • 9
  • 69
  • 120
  • 1
    Please, post the logcat output (stacktrace) for more info – stan0 Feb 25 '14 at 16:56
  • @stan0 I wish that I could. I am using the `EasyTracker` in analytics so all I get is the exception name and line number. I can't seem to reproduce the issue locally. – theblang Feb 25 '14 at 17:03
  • When are you creating and executing Task? – Robby Pond Feb 25 '14 at 17:17
  • @RobbyPond If there are currently no results in the database then in `onActivityCreated`. Otherwise, in `onOptionsItemSelected` when they hit the refresh action bar item. – theblang Feb 25 '14 at 17:28
  • 1
    I don't think we can also with the amount of code. Why are you using both a Loader and an AsyncTask to query the database? – Robby Pond Feb 25 '14 at 17:31
  • @RobbyPond I read that is how I should refresh a `ListView` rather than `notifyDataSetChanged`. At one point I was requerying within the AsyncTask but switched over to Loaders. – theblang Feb 25 '14 at 17:39
  • I was able to reproduce the error! It happens if I initiate the `Task` and then immediately hit back. When The Task completes we are no longer on the activity that contains the fragment so `getLoaderManager` fails. – theblang Feb 25 '14 at 19:57
  • @RobbyPond After doing some reading and thinking, I think I see what you mean. Since I am already doing a network call in `AsyncTask` I can just manipulate the adapter's cursor there, rather than also using a `Loader`. Would you never need a Loader then if you are refreshing data already in an asynchronous way? – theblang Feb 25 '14 at 21:12
  • @RobbyPond I completely gutted the `Loader` and just use `swapCursor` in my `AsyncTask`. You were correct in suggesting that I shouldn't be using both a `Loader and AsyncTask`. If you want to put your comment as an answer I will accept. – theblang Mar 19 '14 at 19:18
  • Uh, what's the IllegalStateException say? Any caused-by messages? – Gray May 30 '18 at 19:59

1 Answers1

0

Some of my thoughts about your case:

  1. Loader has various issues, especially when used with fragments. I could advise you to use ViewModel, but I think they are both based on same code. You could use a normal thread and then use something like EventBus to publish the result, but maybe it's an overkill

  2. I can see that you've forgot to set the classes as static, which means they have a reference to the hosting class.

  3. Is it possible for you to merge the async code into one AsyncTaskLoader ?

  4. You can try my general solution of using AsyncTaskLoader, here.

  5. Maybe instead of a restart, first destroy and then call initLoader again.

  6. AsyncTask should only be used in small cases, where you don't care if the result is gone (example when you change orientation). A good example is for a list of items to show.

  7. I think Google mentioned on Google IO 2018 that the issues with Loaders will be fixed. Not sure if in support library 28, or on AndroidX library. I suggest to check it out. The main thing to remember is to use it only on the UI thread.

android developer
  • 114,585
  • 152
  • 739
  • 1,270