Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NullPointerException in onLoaderFinished using SimpleCursorAdapter

I have switched from a ResourceCursorAdapter where I used newView and bindView to a SimpleCursorAdapter where I am using only the getView method.

Now I have an error in onLoaderFinished. Although it gives me NullPointerException on adapter.swapCursor(cursor) both my adapter and cursor object are NOT null. I will post all of my code below. Any help is greatly appreciated (not got much hair left to pull out).

import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.ResourceCursorAdapter;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.CheckBox;
import android.widget.ListView;
import android.widget.TextView;

public class ContactSelect extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor> {
private static final int LOADER_ID = 1;
private MyAdapter adapter;
private ListView list;
private View row;
private SparseBooleanArray checkedState = new SparseBooleanArray();

@SuppressLint({ "NewApi", "NewApi" })
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
    setContentView(R.layout.activity_contact_select);       

    adapter = new MyAdapter(this, R.layout.contacts_select_row, null, null, null, 0);     

    getSupportLoaderManager().initLoader(LOADER_ID, null, this);        

    list = (ListView)findViewById(R.id.list);                 

    list.setAdapter(adapter);
    list.setEmptyView(findViewById(R.id.empty));    

}   

@SuppressLint("NewApi")
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
    final String projection[] = new String[]{ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME};
    final Uri uri = ContactsContract.Contacts.CONTENT_URI;

    final String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1" + 
    " AND " + ContactsContract.Contacts.IN_VISIBLE_GROUP + " =1";

    final String order = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";

    final CursorLoader loader = new CursorLoader(this, uri, projection, selection, null, order);

    return loader;      
}

public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    for(int i=0;i<cursor.getCount();i++){
        checkedState.put(i, false);
    }

    adapter.swapCursor(cursor);                 
}

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

private class MyAdapter extends SimpleCursorAdapter implements OnClickListener{
    private CheckBox markedBox;
    private TextView familyText;
    private Context context;
    private Cursor cursor;

    public MyAdapter(Context context, int layout, Cursor c, String[] from,
            int[] to, int flags) {
        super(context, layout, c, from, to, flags);

        this.context = context;
        this.cursor = getCursor();
    }

    @Override
    public View getView(int position, View view, ViewGroup group) {

        final LayoutInflater li = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        row = li.inflate(R.layout.contacts_select_row, group, false);

        view.setTag(cursor.getPosition());
        view.setOnClickListener(this);

        familyText = (TextView)view.findViewById(R.id.contacts_row_family_name);
        markedBox = (CheckBox)view.findViewById(R.id.contacts_row_check);
        familyText.setText(cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME)));

        boolean currentlyChecked = checkedState.get(cursor.getPosition());
        markedBox.setChecked(currentlyChecked);     


        setProgressBarIndeterminateVisibility(false);

        return super.getView(position, view, group);
    }

    public void onClick(View view) {
        int rowId = (Integer)view.getTag();
        Log.d("OnClick", String.valueOf(rowId));
        boolean currentlyChecked = checkedState.get(rowId);
        markedBox.setChecked(!currentlyChecked);
        checkedState.put(rowId, !currentlyChecked);
        Log.d("checkedState", "checkedState(" + rowId + ") = " + checkedState.get(rowId));
    }       
      }     
}
like image 869
Stephen Avatar asked Sep 02 '12 11:09

Stephen


2 Answers

adapter = new MyAdapter(this, R.layout.contacts_select_row, null, null, null, 0);

and your MyAdapter class you are passing null cursor

  public MyAdapter(Context context, int layout, Cursor c, String[] from,
            int[] to, int flags) {
        super(context, layout, c, from, to, flags);

        this.context = context;
        this.cursor = getCursor();
    }   
like image 29
Mohammod Hossain Avatar answered Oct 31 '22 18:10

Mohammod Hossain


A call of the swapCursor method of the SimpleCursorAdapter class will trigger a function which maps the column names from the String array provided to the constructor(the 4th parameter) to an array of integers representing the columns indexes. As you pass null in the constructor of MyAdapter for the String array representing the columns names from the cursor, this will throw a NullPointerException later when the swapCursor will try to make the mapping(the NullPointerException should appear in a method findColumns, which is the actual method that uses the column names String array).

The solution is to pass a valid String array, you may also want to do this for the int array representing the ids for the views in which to place the data:

String[] from = {ContactsContract.Contacts.DISPLAY_NAME};
int[] to = {R.id.contacts_row_family_name, R.id.contacts_row_check};
adapter = new MyAdapter(this, R.layout.contacts_select_row, null, from, to, 0);

I don't know what you are trying to do but your implementation of the getView method is not quite right:

You do the normal stuff for the getView method(creating layouts, searching views, binding data) and then you simply return the view from the superclass(?!?), you'll probably just see the default layout with nothing in it.

The way you wrote the getView method is not very efficient, you may want to look into view recycling and the view holder pattern.

cursor.getPosition() will not do what you want as you don't move the cursor to the correct position. By default cursors based adapters will do this for you in the getView method, but, as you overrode the method it's your job to move the cursor's position.

You should leave the getView method and use the two methods newView and bindView as they offer a better separation of the logic.

like image 109
user Avatar answered Oct 31 '22 17:10

user