9

First I should mention that I am using the ActionBarSherlock library for backwards compatibility.

I have an activity which adds a ListFragment when it is first started. I have a custom Loader which I implemented and follows the AsnycTaskLoader example very closely. My ListFragment implements the LoaderCallbacks<Cursor> interface. All the appropriate callback methods are called when the fragment is added (onCreateLoader() , onLoaderFinished() ) and when it is replaced (onLoaderReset() ).

My onActivityCreated(Bundle) method looks like this:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mAccountsDbAdapter = new AccountsDbAdapter(getActivity().getApplicationContext());

    setHasOptionsMenu(true);
    mCursorAdapter = new AccountsCursorAdapter(getActivity()
            .getApplicationContext(), R.layout.list_item_account, null,
            new String[] { DatabaseHelper.KEY_NAME },
            new int[] { R.id.account_name }, 0);

    setListAdapter(mCursorAdapter); 
    getLoaderManager().initLoader(0, null, this);
}

Later on, the ListFragment is replaced with another Fragment B. When the user presses the back button, Fragment B is removed and the ListFragment is added again. However, the list is empty and only the android:empty elements are displayed and none of the LoaderCallback methods are called. I can use the debugger to determine that getLoaderManager().initLoader(0, null, this); is actually called, but nothing else. When I change it to getLoaderManager().restartLoader(0, null, this);, the callbacks get called, but still my list remains empty (although there is data, the view is not refreshed).

How can I get my ListFragment to refresh itself when it is returned to the layout? Has anyone encountered this before, how did you fix it?

FYI, here are my callback methods

    @Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    return new AccountsCursorLoader(this.getActivity()
            .getApplicationContext());
}

@Override
public void onLoadFinished(Loader<Cursor> loaderCursor, Cursor cursor) {
    mCursorAdapter.swapCursor(cursor);
    mCursorAdapter.notifyDataSetChanged();
}

@Override
public void onLoaderReset(Loader<Cursor> arg0) {
    mCursorAdapter.swapCursor(null);
}

Some notes:

  1. I cannot use the setListShown(true) methods in the example because I get an IllegalStateException that it cannot be used with a custom content view.
  2. My AccountsCursorAdapter extends a SimpleCursorAdapter and modifies only the bindView() method.
codinguser
  • 5,562
  • 2
  • 33
  • 40
  • I ran into the same problem, so I am also interested in a solution to this. For now I just start another Activity as a workaround, but that should not be necessary. – Jan-Henk Jun 04 '12 at 10:33
  • @Jan-Henk I found a solution to my question (see my answer below). I don't know if it applies to your situation as well. – codinguser Jun 14 '12 at 21:38

7 Answers7

5

Eureka!!! I found it (by accident, of course)

TL;DR;

In the AccountsListFragment, I now create my database adapter (AccountsDatabaseAdapter) in the onCreate() method and close it in the onDestroy() method and it now works. Previously I was creating my adapter in the onActivityCreated() and closing in onDestroyView(). Note that I am not referring to the ListAdapter, but rather to my database interfacing AccountsDbAdapter.

Attempt at long explanation:

Well, I was doing this because I thought that getting context through getActivity() will not be possible in the onCreate() method. It turns out you can getActivity() even before onActivityCreated() is called.

But I cannot really explain why this now works because the Loader has its own DatabaseAdapter object which it uses to retrieve the data. If I were to guess, I would say that the same database object is returned for both database adapters (the database is cached). Which would mean that when I close one in onDestroyView(), the other is also closed and the cursor data set becomes invalid which results in an empty list view. The loader does not reload because it thinks the data has not changed.

But even this explanation does not satisfy me completely, because some of the suggested solutions here which I tried like force restarting the loader each time did not work. (in the loadInBackground() method, a new DatabaseAdapter is created each time).

Anyway, if you happen to have a better understanding of what is going on, please hammer away in the comments.

Thanks everyone for the help with this!

codinguser
  • 5,562
  • 2
  • 33
  • 40
  • I'll have a look at this tomorrow, but nice to see you found a solution for your problem. – Jan-Henk Jun 14 '12 at 21:57
  • In my code I moved the creation of the ResourceCursorAdapter from the onActivityCreated() to the onCreate() method, and this fixes the problem for me. I don't have a good understanding of this fix either, but one thing I know is that the onCreate() method is only run once, while the onActivityCreated() method is run every time a fragment becomes visible on the screen. It seems likely this has something to do with the problem. But thanks for your fix! – Jan-Henk Jun 15 '12 at 11:46
  • good job @codinguser ! I'm also still itching to know exactly why this is working and why force restarting the loader didn't work! I wonder if you moved that same code to `onCreateView()` if the behavior would still work – wnafee Jun 17 '12 at 13:13
  • @Jan-Henk I am following the "global DatabaseHelper" approach. I create the `SqliteOpenHelper` in `onCreate()` of my `Application` sub-class. I then use this instance of `SqliteOpenHelper` everywhere else in my application (`Fragment`s, `Services`s, `Loader`s, `CursorAdapter`s ... I'm trying to apply your (and @codinguser s) explanation to my case to decide how I should go about doing things. Any inputs? – curioustechizen Jun 25 '12 at 10:34
  • 1
    In my case I found a fix that worked but that I do not really understand myself. Furthermore, I did not encounter this problem ever before, while I created several similar pieces of code. From now on I will just always instantiate my ListAdapters in the onCreate() methods of my fragments to avoid this problem. – Jan-Henk Jun 25 '12 at 12:06
  • Hi, I experienced a similar problem. Please read my solution and see if it applies to you. Thank you. – Some Noob Student May 25 '13 at 20:27
2

Have you tried forcing the loader to restart in your activity, just after underlying data set is changed?

Try:

getLoaderManager().restartLoader(0, null, this);
siefca
  • 1,007
  • 13
  • 16
1

Try forceload():

getLoaderManager().getLoader( 0 ).forceLoad();
Christian
  • 1,830
  • 15
  • 26
1

What the Adapter sees:

The reason why you observe an empty ListView is because the position of the Cursor that was returned is STILL pointing at the last element. When you swap() the Cursor to an Adapter, the Adapter tries to iterate using a while(Cursor.moveToNext()) loop. Because that loop always evaluates FALSE, your ListView gives you the illusion of an empty Cursor.

Print the values of Cursor.getCount() and Cursor.getPosition() in onLoadFinished(). If I am correct, these two values should be equal. This clash of indexes creates the above illusion.

Why does the Adapter see this:

Loaders will re-use a Cursor whenever possible. If you request a Loader for a set of data that has not changed, the loader is smart and return the Cursor via onLoadFinished without doing any additional work, not even setting the position of the Cursor to -1.

ANS Call Cursor.moveToPosition(-1) in onLoadFinished() manually to work around this problem.

Some Noob Student
  • 14,186
  • 13
  • 65
  • 103
0

In my case I found out that the ListView simply was not drawn on the screen for some reason. If I went to the home screen and then switched back to the activity everything would appear as expected. So I searched for a method to force a redraw of the screen and I found the following:

Force a full screen Activity to remeasure/redraw on resume?

Based on the answer to that question I added the following code to the onActivityCreated() method of my ListFragment:

getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 0);

With this code in place everything works as expected. But this is still a workaround, so if someone gives an answer to this question with a better solution or explanation, I will award them the bounty.

Community
  • 1
  • 1
Jan-Henk
  • 4,864
  • 1
  • 24
  • 38
  • Thanks for the answer Jan (and for setting the bounty). I will try this approach when I get to my dev machine. But the thing is, even if you force a redraw, if the problem is with the cursorloader or some other callback mechanism, will it not just still redraw an empty view? In my case, `initLoader()` is called, but `onLoadFinished()` is never called, which (I think) means data not actually loaded. – codinguser Jun 04 '12 at 17:32
0

The problem could come from getLoaderManager() which would not send the callback to the correct place.

If you are using ActionBarSherlock then it means you are on Android 2.x right? Have you considered using Android support package v4? http://developer.android.com/sdk/compatibility-library.html

You would then use android.support.v4.app.ListFragment and get your LoaderManagerby calling getActivity().getSupportLoaderManager()

EricLarch
  • 5,653
  • 5
  • 31
  • 37
  • I am testing both under Android 2.2 and 4.0.4. It doesn't work in either case. I also tried `getActivity().getSupportLoaderManager()` and it still does not work. Thanks for the suggestion though. – codinguser Jun 09 '12 at 20:20
0

Since you are using a custom loader, do you know if you have the same issues when using the native CursorLoader? If not, then perhaps, you can compare your Loader with the Android source and see if they are doing something differently?

https://github.com/android/platform_frameworks_base/blob/master/core/java/android/content/CursorLoader.java

stuckless
  • 6,515
  • 2
  • 19
  • 27
  • Thanks for the response. I cannot test the default CursorLoader because I do not have a ContentProvider for my database (and I was trying to avoid having to create one since I don't need it). The code for the custom `DatabaseCursorLoader` which I wrote is available here: http://goo.gl/flHZk – codinguser Jun 09 '12 at 20:05
  • The big difference that I see with yours is that is is not using the `ForceLoadContentObserver` (http://developer.android.com/reference/android/content/Loader.ForceLoadContentObserver.html) which the `CursorLoader` does. – stuckless Jun 09 '12 at 20:53
  • Added one and registered it with the cursor just like in the CursorLoader example, but nothing came of it. – codinguser Jun 11 '12 at 08:29