Melanie
Melanie

Reputation: 3111

CursorLoader not displaying data in Fragment

I have an app which does the following: an Activity with a ListView displays three items: Beginning, Intermediate, Advanced. Depending on the user's choice, the app should go to a Fragment with a ListView which displays all items from the underlying SQLiteDatabase where Level=Beginning or Intermediate or Advanced. This second step is not working for me. I've verified through the debugger that I have data in my database, so that's not the problem. Here's my code in the initial Activity - this is from the onItemClick method:

final String[] values = new String[] {
            "Beginning", "Intermediate", "Advanced"
    };
public void onItemClick(AdapterView<?> parent, View view, int position,
                long id) {
            Intent choice = new Intent(getApplicationContext(), com.MyKnitCards.project.StitchList.class);
            Bundle dataBundle = new Bundle();
            String chosenValue = values[position];
            dataBundle.putString("Level",chosenValue);
            choice.putExtras(dataBundle);
            try {
                startActivity(choice);
}
        }

Here's the StitchList class, which is the Fragment class, and which is referenced in the code above:

public class StitchList extends FragmentActivity {
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.stitchfragment);
}

}

Here's the XML file for the Fragment, called stitchfragment.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<fragment android:id="@+id/frag_stitchlist"
android:layout_width="200dp"
android:layout_height="match_parent"
android:layout_marginTop="?android:attr/actionBarSize"
class="com.MyKnitCards.project.ListFrag" />
<fragment android:id="@+id/frag_stitchdetail"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.MyKnitCards.project.DetailFrag" />
</LinearLayout>

Here's the ListFrag class. I'm using the Support Library and a CursorLoader with a ContentProvider, called StitchProvider; the SQLiteDatabase is called SQLData:

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

private static final String STITCHTABLE_BASEPATH = "MyStitches_tbl";
private static final String AUTHORITY = "com.MyKnitCards.project.SQLData";
public static final Uri STITCHES_URI = Uri.parse("content://" + AUTHORITY + "/" + STITCHTABLE_BASEPATH);
private static final String[] PROJECTION = new String[] { "_id", "stitchname" };
private SimpleCursorAdapter mAdapter;
private static final int LOADER_ID = 0;

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
}

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    Intent myData = getActivity().getIntent();
    Bundle info = myData.getExtras();   

    String[] dataColumns = { "stitchname", "_id" };
    int[] viewIDs = { R.id.frag_stitchlist };
    mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_1, null, dataColumns, viewIDs, 0);
    setListAdapter(mAdapter);
    getLoaderManager().initLoader(0, info, (LoaderCallbacks<Cursor>) this); 

}

public void onListItemClick(ListView l, View v, int position, long id) {
    String item = (String) getListAdapter().getItem(position);
    DetailFrag frag = (DetailFrag) getFragmentManager().findFragmentById(R.id.frag_stitchdetail);
    if (frag != null && frag.isInLayout()) {
        frag.setText(item);
    }
}

public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    String selection = "stitchlevel='" + args.getString("Level") + "'";
    return (Loader<Cursor>) new CursorLoader(getActivity(), STITCHES_URI,
            PROJECTION, selection, null, null); 
}

public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    switch (loader.getId()) {
      case LOADER_ID:
        mAdapter.swapCursor((android.database.Cursor) cursor);
        break;
    }

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

}

}

The onListItemClick method is for displaying data in the details Fragment, but I'm never getting this far. The list of items retrieved from the database based on the data in the Intent is never displayed.

Some possible issues/questions come to mind:

1) Is the syntax of my query in the onCreateLoader method correct? It should read, "stitchlevel='Beginning'", for example. I know the data in args.getString("Level") is valid because I've followed it through in the debugger, but is the syntax correct here? I'm assuming it should be standard SQL syntax, but... 2) In the assignment of value to viewIDs in the Fragment's onActivityCreated method, am I pointing to the correct place? 3) Similarly, in the next line, the SimpleCursorAdapter, is android.R.layout.simple_list_item_1 correct here? Or should it perhaps be R.layout.stitchfragment? 4) I put a breakpoint on the ContentProvider's query method, I never hit the breakpoint. Shouldn't I when the CursorLoader gets created? Furthermore, if I put a breakpoint on the List Fragment's onLoadFinished method, the cursor is null. What am I missing 5) I was told that dataColumns in the Fragment's onActivityCreated method should contain my table's _id column. Is that true? It seems odd to me, since the list will only display one piece of information: the stitchname. However, whether _id is included or not here, the Fragment list does not display anything 6) Is it proper for me to pass the Bundle, info, to the CursorLoader through getLoaderManager.initLoader, as I do in the Fragment's onActivityCreatedMethod? 7) I've seen examples of setListAdapter being called before getLoaderManager().initLoader() in the Fragment's onActivityCreated method and I've seen examples where setListAdapter is called after. Which is correct? Neither seems to help me....

I have been able to get a Fragment to display if I simply do this:

Intent myData = getActivity().getIntent();
    Bundle info = myData.getExtras();
    String level = info.getString("Level");
    String[] values = new String[] {level, level};
    ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, values);
    setListAdapter(adapter);

So I know I can display data in the Fragment; it just doesn't seem to be working with the CursorLoader and ContentProvider. I hope this isn't information overload; my apologies if it is. If anyone has any ideas, I'd be most grateful.

Upvotes: 0

Views: 3140

Answers (1)

user
user

Reputation: 87064

1) Is the syntax of my query in the onCreateLoader method correct? It should read, "stitchlevel='Beginning'", for example. I know the data in args.getString("Level") is valid because I've followed it through in the debugger, but is the syntax correct here? I'm assuming it should be standard SQL syntax, but

Yes, it's correct. Even better would be:

String selection = "stitchlevel= ?";
String selectionArgs = new String[] {args.getString("Level")};
return new CursorLoader(getActivity(), STITCHES_URI, PROJECTION, selection, selectionArgs, null); 

2) In the assignment of value to viewIDs in the Fragment's onActivityCreated method, am I pointing to the correct place? 3) Similarly, in the next line, the SimpleCursorAdapter, is android.R.layout.simple_list_item_1 correct here? Or should it perhaps be R.layout.stitchfragment?

No. The int array that you supply to the adapter represents the ids from the row layout that is used in the adapter and not the id of the fragment like you wrote it. If you use the android.R.layout.simple_list_item_1(which consists of only a TextView) than you have to provide the id of that TextView, which is android.R.id.text1:

String[] dataColumns = { "stitchname"};
int[] viewIDs = { android.R.id.text1 };
mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_1, null, dataColumns, viewIDs, 0);

4) I put a breakpoint on the ContentProvider's query method, I never hit the breakpoint. Shouldn't I when the CursorLoader gets created? Furthermore, if I put a breakpoint on the List Fragment's onLoadFinished method, the cursor is null. What am I missing

I think you didn't do what I recommended you on your last question. The provider's query method should have been at least called and this not happening just leaves one option. I've noticed that you cast the Loaders and you're missing the Override annotation on the callback methods from LoaderManager.LoaderCalbbacks. Add the Override annotation to those methods:

@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    String selection = "stitchlevel= ?";
    String selectionArgs = new String[] {args.getString("Level")};
    return new CursorLoader(getActivity(), STITCHES_URI,
            PROJECTION, selection, selectionArgs, null); 
}

and see what happens. If you get errors make sure you used the correct imports and either only use the classes from the compatibility package or the normal ones(you could easily delete all imports from the file and then re-import everything and use the classes with android.support.v4). Just a wild guess.

5) I was told that dataColumns in the Fragment's onActivityCreated method should contain my table's _id column. Is that true? It seems odd to me, since the list will only display one piece of information: the stitchname. However, whether _id is included or not here, the Fragment list does not display anything

Cursor based adapters require that the used Cursor must contain a column named _id(see the documentation of the CursorAdapter class, the super class of SimpleCursorAdapter). It doesn't need to be present in the String array which is set into the adapter.

6) Is it proper for me to pass the Bundle, info, to the CursorLoader through getLoaderManager.initLoader, as I do in the Fragment's onActivityCreatedMethod?

Yes. But regarding the id, be consistent, if you're going to use LOADER_ID use it everywhere and not just in some places in your code. Or even better, if you're going to use just one Loader just use 0 everywhere(so there is no need to test for the LOADER_ID in the onLoadFinished method).

7) I've seen examples of setListAdapter being called before getLoaderManager().initLoader() in the Fragment's onActivityCreated method and I've seen examples where setListAdapter is called after. Which is correct?

Both can be used. I would set the adapter and then initialize the Loader.

Upvotes: 1

Related Questions