Search code examples
androidandroid-listviewandroid-sqliteandroid-cursoradapter

ListView in ListFragment not Loading Items


I have a ListFragment with a Custom CursorAdapter connected to a database. The items in the database are not appearing in the ListView, including the ListHeader, and instead I see only a spinning wheel.

I had my app working fine with a SimpleCursorAdapter in the ListFragment before. I have tested this on non-empty databases. The only clue I have as to why this is not working is that I get the following error in LogCat when I try the "Delete All" option in the ContextActionMenu.

ViewRootImpl sendUserActionEvent() == null

Here is the ListFragment:

import com.actionbarsherlock.app.SherlockListFragment;
import com.actionbarsherlock.view.ActionMode;
import com.actionbarsherlock.view.ActionMode.Callback;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;

import info.mariegrap.dancejournal.R.id;
import info.mariegrap.database.DanceContentProvider;
import info.mariegrap.database.DanceDatabaseHelper;
import info.mariegrap.database.StyleCursorAdapter;
import info.mariegrap.database.StyleTable;
import info.mariegrap.model.Style;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ListView;
import android.database.Cursor;
import android.database.SQLException;

public class StyleFragment extends SherlockListFragment implements LoaderManager.LoaderCallbacks<Cursor>{

    private static final int LOADER_ID = 0;

    private Context context;
    private OnStyleSelectedListener callback;
    private StyleCursorAdapter styleAdapter;
    private DanceDatabaseHelper myDbHelper;
    private Callback mActionModeCallback;
    private ActionMode mActionMode;
    private long selectedId;

    @Override
    public void onActivityCreated(Bundle savedInstanceState){
        super.onActivityCreated(savedInstanceState);
        context = this.getActivity();
        setHasOptionsMenu(true);
        //setRetainInstance(true);
        this.getListView().setBackgroundColor(getResources().getColor(R.color.style_background));
        //this.getListView().setDividerHeight(0);
        this.getListView().setScrollingCacheEnabled(true);
        this.getListView().addHeaderView(new ListHeader(context,
                this.getResources().getString(R.string.list_header_style_title), 
                this.getResources().getString(R.string.list_header_style_subtitle)));
        this.setActionModeCallback();
        this.setLongClickListener();

        /*myDbHelper = new DanceDatabaseHelper(this.context, null,
                null, 1);
        try {
             myDbHelper.openDataBase();;
        }catch(SQLException sqle){
                throw sqle;
        }*/



        this.styleAdapter = new StyleCursorAdapter(context, null, 0);
        getLoaderManager().initLoader(LOADER_ID, null, this);
        this.getListView().setAdapter(this.styleAdapter);
        Log.d("mgrap", "Adapter: " + styleAdapter);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        try {
            callback = (OnStyleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnStyleSelectedListener");
        }
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id){
        if (position == 0){
            return;
        }
        this.setListAdapter(null);
        this.getListView().getChildAt(position);
        callback.onStyleSelected(position);
    }


    public interface OnStyleSelectedListener {
        /** Called by StyleFragment when a list item is selected */
        public void onStyleSelected(int position);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 
        inflater.inflate(R.menu.style_actionbar, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.style_add:
                ContentValues cv = new ContentValues();
                cv.put(StyleTable.STYLE_KEY_NAME, "Tada!");
                Uri uri = Uri.parse("content://info.mariegrap.dancejournal.provider/style_table/0");
                Uri idUri = context.getContentResolver().insert(uri, cv);
                //styleAdapter.notifyDataSetChanged();
                fillData();
                return true;
            case R.id.style_delete_all:
                Uri delete_uri = Uri.parse("content://info.mariegrap.dancejournal.provider/style_table");
                context.getContentResolver().delete(delete_uri, null, null);
                //styleAdapter.notifyDataSetChanged();
                fillData();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    protected void fillData() {
        this.getSherlockActivity().getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
        this.getListView().setAdapter(styleAdapter);
    }   

    protected void deleteStyle(long id) {
        Uri uri = Uri.parse("content://info.mariegrap.dancejournal.provider/style_table/" + id);
        this.getSherlockActivity().getContentResolver().delete(uri, null, null);
        //styleAdapter.notifyDataSetChanged();
        fillData();
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        String[] projection = { StyleTable.STYLE_KEY_ID, StyleTable.STYLE_KEY_NAME };
        return new CursorLoader(this.context,
            DanceContentProvider.CONTENT_URI_STYLE, projection, null, null, null);
    }

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

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

    private void setActionModeCallback(){
        mActionModeCallback = new Callback() {  

            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                MenuInflater inflater = mode.getMenuInflater();
                inflater.inflate(R.menu.style_context_menu, menu);
                return true;
            }

            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                return false;
            }

            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.style_delete:
                        deleteStyle(selectedId);
                        mode.finish();
                        return true;

                    default:
                        return false;
                }
            }

            @Override
            public void onDestroyActionMode(ActionMode mode) {
                mActionMode = null;
            }
        };
    }

    private void setLongClickListener(){
        this.getListView().setOnItemLongClickListener (new OnItemLongClickListener() {

            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                if (mActionMode != null || position == 0) {
                    return false;
                }
                mActionMode = getSherlockActivity().startActionMode(mActionModeCallback);
                selectedId = id;
                return true;
            }
        });
    }
}

Here is the Custom CursorAdapter:

import view.StyleListItem;
import info.mariegrap.model.Style;
import android.content.Context;
import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

public class StyleCursorAdapter extends CursorAdapter {

    public StyleCursorAdapter(Context context, Cursor c, int flags) {
        super(context, c, flags);
    }

    @Override
    public void bindView(View arg0, Context arg1, Cursor arg2) {
        int id = arg2.getInt(StyleTable.STYLE_COL_ID);
        String name = arg2.getString(StyleTable.STYLE_COL_NAME);

        Style style = new Style(name, id);
        StyleListItem listItem = (StyleListItem) arg0;
        listItem.setStyle(style);   
    }

    @Override
    public View newView(Context arg0, Cursor arg1, ViewGroup arg2) {
        int id = arg1.getInt(StyleTable.STYLE_COL_ID);
        String name = arg1.getString(StyleTable.STYLE_COL_NAME);

        Style style = new Style(name, id);
        StyleListItem listItem = new StyleListItem(arg0, style);
        listItem.setStyle(style);
        Log.d("mgrap", "Adapter view: " + listItem.getStyleId());
        return listItem;
    }

}

Here is the ListItem View:

public class StyleListItem extends LinearLayout {

    private TextView styleView;

    private Style style;

    public StyleListItem(Context context, Style style) {
        super(context);
        LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.style_row, this, true);

        styleView = (TextView)findViewById(R.id.style_label);
        Log.d("mgrap", "Style View: " + styleView);
        setStyle(style);
    }

    public void setStyle(Style style) {
        this.style = style;
        displayStyle(this.style);
    }

    public Style getStyle(){
        return style;
    }

    private void displayStyle(Style style) {
        if (style != null) {
            styleView.setText(style.getName());
        }
    }

    public int getStyleId() {
        return styleView.getId();
    }
}

Here is the layout xml for the list items:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <ImageView
        android:id="@+id/icon"
        android:layout_width="30dp"
        android:layout_height="24dp"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="8dp"
        android:src="@drawable/ic_launcher" >
    </ImageView>

    <TextView
        android:id="@+id/style_label"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="6dp"
        android:lines="1"
        android:text="@+id/TextView01"
        android:textSize="24sp" 
        >
    </TextView>
</LinearLayout> 

Here is the xml layout for the list header:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/list_header_background_color"
    android:layout_marginBottom="5dip" >
    <LinearLayout
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:orientation="vertical"
        android:paddingTop="5dip"
        android:paddingLeft="10dip"
        android:paddingBottom="13dip"
        android:layout_centerVertical="true"
        android:layout_alignParentLeft="true">
        <TextView
        android:id="@+id/listview_header_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/list_header_style_title"
        android:textSize="17sp"
        android:textStyle="bold"
        android:textColor="@color/list_header_title_color"
        />

        <TextView
        android:id="@+id/listview_header_subtitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/list_header_style_subtitle"
        android:textSize="13sp"
        android:textColor="@color/list_header_subtitle_color"
        />
    </LinearLayout>

    <ImageView
        android:id="@+id/list_header_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="20dp"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="13dp"
        android:visibility="gone"
        android:contentDescription="@string/image_desc"
    />

</RelativeLayout>

And here is the class that defines the style database table:

import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

public class StyleTable {
    /** Style table in the database. */
    public static final String TABLE_STYLE = "style_table";

    /** Style table column names and IDs for database access. */
    public static final String STYLE_KEY_ID = "_id";
    public static final int STYLE_COL_ID = 0;

    public static final String STYLE_KEY_NAME = "name";
    public static final int STYLE_COL_NAME = STYLE_COL_ID + 1;

    /** SQLite database creation statement. Auto-increments IDs of inserted
     * styles. Style IDs are set after insertion into the database. */
    public static final String DATABASE_CREATE = "create table " +
            TABLE_STYLE + " (" +
            STYLE_KEY_ID + " integer primary key autoincrement, " +
            STYLE_KEY_NAME  + " text);";

    /** SQLite database table removal statement. Only used if upgrading
     * database. */
    public static final String DATABASE_DROP = "drop table if exists " +
            TABLE_STYLE;

    /**
     * Initializes the database.
     *
     * @param database
     * The database to initialize.
     */
    public static void onCreate(SQLiteDatabase database) {
        database.execSQL(DATABASE_CREATE);
    }

    /**
     * Upgrades the database to a new version.
     *
     * @param database
     * The database to upgrade.
     * @param oldVersion
     * The old version of the database.
     * @param newVersion
     * The new version of the database.
     */
    public static void onUpgrade(SQLiteDatabase database, int oldVersion,
    int newVersion)
    {
        Log.w("mgrap", "updating database...");
        database.execSQL(DATABASE_DROP);
        onCreate(database);
    }
}

Solution

  • It's hard to say without having some more logging to go with, but there are a couple things that jump out:

    1. The adapter is being set on the list view directly instead of calling setAdapter on the ListFragment. This usually causes problems.
    2. You're setting the list view's adapter after initializing the loader. This might not be a race condition since the callbacks would probably be called in the main loop after your call is finished, but I wouldn't count on it.