22

I was going through some of my code and I realized I don't actually know how a CursorLoader and LoaderManager combination used with a CursorAdapter connect. Heres the part that I am confused in.

agendaAdapter = new MyAgendaAdapter(this, null);

makeProviderBundle(new String[] {"_id", "event_name", "start_date", "start_time",
    "end_date", "end_time", "location"}, "date(?) >= start_date and date(?) <= end_date", 
new String[]{getChosenDate(), getChosenDate()}, null);

getLoaderManager().initLoader(0, myBundle, MainDisplayActivity.this);
list.setAdapter(agendaAdapter);

So how does the query() method from my custom ContentProvider know to send it to that specific CursorAdapter? I just don't see the connection. I understand everything else in that but what this question is on. Oh and I should mention, the code works fine.

Alex Lockwood
  • 83,063
  • 39
  • 206
  • 250
Andy
  • 10,553
  • 21
  • 75
  • 125
  • 1
    I am beginning to suspect that you are asking these questions just to screw with me, knowing that I can't help but answer (even when I am at work lol) – Alex Lockwood Jun 22 '12 at 14:18
  • idk what you are talking about :) but if you're there... – Andy Jun 22 '12 at 16:22

1 Answers1

84

First of all, check out the code sample at this post and this post for an even more in-depth look into how the process works.

And now, to answer your questions...

How does the query() method from my custom ContentProvider...?

Well, first remember that getContentResolver().query() doesn't invoke the content provider's query method directly. You are invoking the content resolver's query method, which parses the Uri, determines the provider you wish to invoke, and then calls your provider's query method.

How does the query get sent to that specific CursorAdapter?

I'll walk you through the process using the API Demos as an example. Note that the API demos uses a ListFragment instead of a ListActivity (the difference is not important in the context of this question).


  1. First, create (and set up) the CursorAdapter.

    mAdapter = new SimpleCursorAdapter(
            getActivity(),
            android.R.layout.simple_list_item_2, 
            null,
            new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
            new int[] { android.R.id.text1, android.R.id.text2 }, 
            0);
    

    After this statement is executed, the SimpleCursorAdapter knows how it should associate the cursor data with your views. Whatever data is in the cursor's Contacts.DISPLAY_NAME column will be associated with the view with id android.R.id.text1, etc.

    Note that you have passed a null cursor as the third argument to the constructor. This is very important, as we have not queried any data yet (this is the LoaderManager and CursorLoader's job).

  2. Next, initialize the loader.

    getLoaderManager().initLoader(0, null, this);
    

    This tells the LoaderManager to create and start the Loader corresponding to id 0.

  3. The LoaderManager calls onCreateLoader(int id, Bundle args).

    onCreateloader returns a subclass of the Loader<Cursor> interface (i.e. a CursorLoader, in this case). This CursorLoader will perform the initial query and will update itself when the data changes.

    If your activity/fragment has more than one loader, then you'd use switch(id) to determine the specific loader that has been instructed to begin the loading process.

  4. The queried cursor is passed to onLoadFinished().

    Immediately after the CursorLoader is instantiated and returned in step 3, the CursorLoader performs the initial query on a separate thread and a cursor is returned. When the CursorLoader finishes the query, it returns the newly queried cursor to the LoaderManager, which then passes the cursor to the onLoadFinished method. From the documentation, "the onLoadFinished method is called when a previously created loader has finished its load."

  5. The queried data is associated with the CursorAdapter.

    mAdapter.swapCursor(data);
    

    Note that onLoadFinished is also typically where you update the activity/fragment's UI with the queried data. This isn't necessary in this case, as we have previously called setListAdapter(mAdapter). The ListFragment knows how to use the CursorAdapter (see step 1)... all we need to do is pass the adapter the cursor with swapCursor, and the ListFragment will take care of displaying the data on the screen for us.

Let me know if you have any questions (or if there are any typos, etc.).


TL;DR

The cursor that contains your queried data is associated with the CursorAdapter in onLoadFinished. This is typically done by calling mAdapter.swapCursor(data).

Matt
  • 74,352
  • 26
  • 153
  • 180
Alex Lockwood
  • 83,063
  • 39
  • 206
  • 250
  • 2
    Fantastic run through on loaders – SeanPONeil Jun 22 '12 at 15:13
  • 13
    Don't thank me... thank all of the crappy, poorly-developed apps on the Android market. They are the reason why I feel responsible for explaining this process in **excessive** detail. :) – Alex Lockwood Jun 22 '12 at 15:42
  • 1
    Hey @AlexLockwood quick question. If I am using a CursorLoader with a LoaderManager I do not need to worry about closing the database or cursors right. But what about when I just use it directly like `getContentResolver().query()`. In that case do I have to explicitly close them, or does all that still get handled by the class itself? – Andy Jul 21 '12 at 20:59
  • If you're using a `CursorLoader` + `LoaderManager` then you should **not** close the `Cursor`s (or else an exception will probably be thrown). The extremely short but detailed explanation of what happens is that the `Activity` tells the `LoaderManager` to perform certain actions during the `Activity` lifecycle (i.e. it will instruct the `LoaderManager` to retain its `Loader`s on a configuration change). The `LoaderManager` will react to these requests by calling the `CursorLoader` methods (i.e. `onStartLoading`, `onStopLoading`, and a bunch of other methods inherited from the `Loader` class). – Alex Lockwood Jul 21 '12 at 21:39
  • 2
    Then, in these methods the `CursorLoader` will close its cursors accordingly (i.e. in [**`onReset`**](http://bit.ly/M3eFqJ), etc.). In other words, it's not the `LoaderManager` closing the cursors... it's the `LoaderManager` telling the `CursorLoader` to close the cursors. – Alex Lockwood Jul 21 '12 at 21:42
  • But yeah... basically you never have to close the cursors when you are using `CursorLoader`s haha. You should close cursors queried with `getContentResolver().query` though, since raw cursors are not managed. – Alex Lockwood Jul 21 '12 at 21:44
  • Where in the Activity/Fragment lifecycle is it best to call getLoaderManager().initLoader? I have looked at Google's sourcecode, and they do it all over the place: some in onCreate, onResume, onActivityCreated, onCreateView – IgorGanapolsky Jan 23 '14 at 20:24
  • 1
    @IgorGanapolsky I remember discussing this with some other Android devs [here](https://twitter.com/cyrilmottier/status/372646529255092224). I always initialize loaders in `onActivityCreated()`. I agree with you that Google's documentation is a little vague about this point... but I have never had any issues with initializing Loaders in `onActivityCreated()`. – Alex Lockwood Jan 23 '14 at 22:11
  • I wanna have more control over my view. How can i replace `SimpleCursorAdapter` with `CursorAdapter` using `CursorLoader`? – Cassio Landim May 26 '15 at 21:50
  • I would like to make something clear for me (and everyone else). After I swap the cursor with oldCursor = cursorAdapter.swapCursor(newCursor), should I close the oldCursor by myself or will that be taken care of or is that even a bad idea? I know that newCursor should not be closed under no circumstances at least unitil it is not swapped for a new one. – f470071 Oct 07 '15 at 18:23