this exception is driving me nuts. When ever my main activity's process gets killed and then restored my app force closes due to a null pointer exception in my expandableListView. This is my first android app and first time writing java so I am a bit of a n00b. It is a timetable app and it is using a Cursor Loader to load in the data to the list from a content provider. What am i doing wrong here? Thanks in advance.
Logcat:
12-03 00:21:42.235: E/AndroidRuntime(2341): FATAL EXCEPTION: main
12-03 00:21:42.235: E/AndroidRuntime(2341): Process: com.crevitus.timetable, PID: 2341
12-03 00:21:42.235: E/AndroidRuntime(2341): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.crevitus.timetable/com.crevitus.timetable.MainActivity}: java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.List.size()' on a null object reference
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2298)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.app.ActivityThread.access$800(ActivityThread.java:144)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1278)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.os.Handler.dispatchMessage(Handler.java:102)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.os.Looper.loop(Looper.java:135)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.app.ActivityThread.main(ActivityThread.java:5221)
12-03 00:21:42.235: E/AndroidRuntime(2341): at java.lang.reflect.Method.invoke(Native Method)
12-03 00:21:42.235: E/AndroidRuntime(2341): at java.lang.reflect.Method.invoke(Method.java:372)
12-03 00:21:42.235: E/AndroidRuntime(2341): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
12-03 00:21:42.235: E/AndroidRuntime(2341): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
12-03 00:21:42.235: E/AndroidRuntime(2341): Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.List.size()' on a null object reference
12-03 00:21:42.235: E/AndroidRuntime(2341): at com.crevitus.timetable.adapter.ExpandableListAdapter.getChildrenCount(ExpandableListAdapter.java:67)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.widget.ExpandableListConnector.refreshExpGroupMetadataList(ExpandableListConnector.java:563)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.widget.ExpandableListConnector.setExpandedGroupMetadataList(ExpandableListConnector.java:758)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.widget.ExpandableListView.onRestoreInstanceState(ExpandableListView.java:1340)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.view.View.dispatchRestoreInstanceState(View.java:13621)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.view.ViewGroup.dispatchThawSelfOnly(ViewGroup.java:2907)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.widget.AdapterView.dispatchRestoreInstanceState(AdapterView.java:795)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2893)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2893)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.view.View.restoreHierarchyState(View.java:13599)
12-03 00:21:42.235: E/AndroidRuntime(2341): at com.android.internal.policy.impl.PhoneWindow.restoreHierarchyState(PhoneWindow.java:1980)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.app.Activity.onRestoreInstanceState(Activity.java:1022)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.app.Activity.performRestoreInstanceState(Activity.java:977)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1161)
12-03 00:21:42.235: E/AndroidRuntime(2341): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2271)
12-03 00:21:42.235: E/AndroidRuntime(2341): ... 10 more
MainActivity Code:
package com.crevitus.timetable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import android.app.AlertDialog;
import android.app.LoaderManager;
import android.content.ContentResolver;
import android.content.CursorLoader;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ExpandableListView.OnGroupExpandListener;
import android.widget.Toast;
import com.crevitus.timetable.adapter.ExpandableListAdapter;
import com.crevitus.timetable.provider.TimetableContentProvider;
import com.crevitus.timetable.service.ReminderHandler;
public class MainActivity extends BaseActivity implements
LoaderManager.LoaderCallbacks<Cursor> {
private ExpandableListAdapter listAdapter;
private ExpandableListView expListView;
private List<String> listDataHeader;
private HashMap<String, List<List<String>>> listDataChild;
private int lastExpandedPosition = -1;
private int gPosition, cPosition;
private static final int MONDAY = 0, TUESDAY = 1, WEDNESDAY = 2, THURSDAY = 3, FRIDAY = 4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
//get data from content provider
getLoaderManager().initLoader(MONDAY, null, this);
getLoaderManager().initLoader(TUESDAY, null, this);
getLoaderManager().initLoader(WEDNESDAY, null, this);
getLoaderManager().initLoader(THURSDAY, null, this);
getLoaderManager().initLoader(FRIDAY, null, this);
// get the listview
expListView = (ExpandableListView) findViewById(R.id.lvExp);
expListView.setLongClickable(true);
//initialise data collections
listDataHeader = new ArrayList<String>();
listDataChild = new HashMap<String, List<List<String>>>();
// Add header data
listDataHeader.add("Monday");
listDataHeader.add("Tuesday");
listDataHeader.add("Wednesday");
listDataHeader.add("Thursday");
listDataHeader.add("Friday");
//get adapter
listAdapter = new ExpandableListAdapter(this, listDataHeader, listDataChild);
// set list adapter
expListView.setAdapter(listAdapter);
// Listview on child click listener
expListView.setOnChildClickListener(new OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
//get data by position
String group = (String) listAdapter.getGroup(groupPosition);
List<String> childList = listDataChild.get(group).get(childPosition);
//launch view activity and pass data
Intent view = new Intent(getApplicationContext(), ViewActivity.class);
Bundle bundle = new Bundle();
bundle.putStringArray("Class", new String[] {childList.get(0), childList.get(1), childList.get(2), childList.get(3), childList.get(4), childList.get(5)});
view.putExtras(bundle);
startActivity(view);
return false;
}
});
expListView.setOnItemLongClickListener(new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (ExpandableListView.getPackedPositionType(id) == ExpandableListView.PACKED_POSITION_TYPE_CHILD)
{
int groupPosition = ExpandableListView.getPackedPositionGroup(id);
int childPosition = ExpandableListView.getPackedPositionChild(id);
longClick( expListView, groupPosition, childPosition);
return true;
}
return false;
}
private void longClick(ExpandableListView expListView, int groupPosition, int childPosition) {
gPosition = groupPosition;
cPosition = childPosition;
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(MainActivity.this);
// set title
alertDialogBuilder.setTitle("Delete Class?");
// set dialog message
alertDialogBuilder
.setMessage("Are you sure you want to delete the class?")
.setCancelable(false)
.setPositiveButton("Yes",new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,int id) {
String group = (String) listAdapter.getGroup(gPosition);
List<String> childList = listDataChild.get(group).get(cPosition);
ContentResolver cr = getContentResolver();
cr.delete(TimetableContentProvider.CONTENT_URI, TimetableContentProvider.KEY_ID + " = ?", new String[] {childList.get(0)});
listAdapter.deleteChild(gPosition, cPosition);
listAdapter.notifyDataSetChanged();
ReminderHandler.cancelAlarm(Integer.parseInt(childList.get(0)), getApplicationContext());
Toast.makeText(getApplicationContext(), "Class Deleted", Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton("No",new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,int id) {
// if this button is clicked, just close
// the dialog box and do nothing
dialog.cancel();
}
});
// create alert dialog
AlertDialog alertDialog = alertDialogBuilder.create();
// show it
alertDialog.show();
}
});//end longclick listener
expListView.setOnGroupExpandListener(new OnGroupExpandListener() {
@Override
public void onGroupExpand(int groupPosition) {
if (lastExpandedPosition != -1 && groupPosition != lastExpandedPosition)
{
expListView.collapseGroup(lastExpandedPosition);
}
lastExpandedPosition = groupPosition;
}
});
}
@Override
public void onResume()
{
super.onResume();
getLoaderManager().restartLoader(MONDAY, null, this);
getLoaderManager().restartLoader(TUESDAY, null, this);
getLoaderManager().restartLoader(WEDNESDAY, null, this);
getLoaderManager().restartLoader(THURSDAY, null, this);
getLoaderManager().restartLoader(FRIDAY, null, this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
CursorLoader loader = null;
switch(id)
{
case MONDAY:
loader = new CursorLoader(this,
TimetableContentProvider.CONTENT_URI,
null,
TimetableContentProvider.KEY_DAY_COLUMN + "=?", new String[] {"Monday"},
null);
break;
case TUESDAY:
loader = new CursorLoader(this,
TimetableContentProvider.CONTENT_URI,
null,
TimetableContentProvider.KEY_DAY_COLUMN + "=?", new String[] {"Tuesday"},
null);
break;
case WEDNESDAY:
loader = new CursorLoader(this,
TimetableContentProvider.CONTENT_URI,
null,
TimetableContentProvider.KEY_DAY_COLUMN + "=?", new String[] {"Wednesday"},
null);
break;
case THURSDAY:
loader = new CursorLoader(this,
TimetableContentProvider.CONTENT_URI,
null,
TimetableContentProvider.KEY_DAY_COLUMN + "=?", new String[] {"Thursday"},
null);
break;
case FRIDAY:
loader = new CursorLoader(this,
TimetableContentProvider.CONTENT_URI,
null,
TimetableContentProvider.KEY_DAY_COLUMN + "=?", new String[] {"Friday"},
null);
break;
}
return loader;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
List<String> splitMon;
List<String> splitTues;
List<String> splitWed;
List<String> splitThurs;
List<String> splitFri;
List<List<String>> monday = new ArrayList<List<String>>();
List<List<String>> tuesday = new ArrayList<List<String>>();
List<List<String>> wednesday = new ArrayList<List<String>>();
List<List<String>> thursday = new ArrayList<List<String>>();
List<List<String>> friday = new ArrayList<List<String>>();
switch(loader.getId())
{
case MONDAY:
while(cursor.moveToNext())
{
splitMon = new ArrayList<String>();
splitMon.add(cursor.getString(0));
splitMon.add(cursor.getString(1));
splitMon.add(cursor.getString(2));
splitMon.add(cursor.getString(3));
splitMon.add(cursor.getString(4));
splitMon.add(cursor.getString(5));
monday.add(splitMon);
}
listDataChild.put(listDataHeader.get(0), monday);
break;
case TUESDAY:
while(cursor.moveToNext())
{
splitTues = new ArrayList<String>();
splitTues.add(cursor.getString(0));
splitTues.add(cursor.getString(1));
splitTues.add(cursor.getString(2));
splitTues.add(cursor.getString(3));
splitTues.add(cursor.getString(4));
splitTues.add(cursor.getString(5));
tuesday.add(splitTues);
}
listDataChild.put(listDataHeader.get(1), tuesday);
break;
case WEDNESDAY:
while(cursor.moveToNext())
{
splitWed = new ArrayList<String>();
splitWed.add(cursor.getString(0));
splitWed.add(cursor.getString(1));
splitWed.add(cursor.getString(2));
splitWed.add(cursor.getString(3));
splitWed.add(cursor.getString(4));
splitWed.add(cursor.getString(5));
wednesday.add(splitWed);
}
listDataChild.put(listDataHeader.get(2), wednesday);
break;
case THURSDAY:
while(cursor.moveToNext())
{
splitThurs = new ArrayList<String>();
splitThurs.add(cursor.getString(0));
splitThurs.add(cursor.getString(1));
splitThurs.add(cursor.getString(2));
splitThurs.add(cursor.getString(3));
splitThurs.add(cursor.getString(4));
splitThurs.add(cursor.getString(5));
thursday.add(splitThurs);
}
listDataChild.put(listDataHeader.get(3), thursday);
break;
case FRIDAY:
while(cursor.moveToNext())
{
splitFri = new ArrayList<String>();
splitFri.add(cursor.getString(0));
splitFri.add(cursor.getString(1));
splitFri.add(cursor.getString(2));
splitFri.add(cursor.getString(3));
splitFri.add(cursor.getString(4));
splitFri.add(cursor.getString(5));
friday.add(splitFri);
}
listDataChild.put(listDataHeader.get(4), friday);
break;
}
listAdapter.notifyDataSetChanged();
expListView.post( new Runnable() {
@Override
public void run() {
switch(Calendar.getInstance().get(Calendar.DAY_OF_WEEK)){
case Calendar.MONDAY:
expListView.expandGroup(0);
break;
case Calendar.TUESDAY:
expListView.expandGroup(1);
break;
case Calendar.WEDNESDAY:
expListView.expandGroup(2);
break;
case Calendar.THURSDAY:
expListView.expandGroup(3);
break;
case Calendar.FRIDAY:
expListView.expandGroup(4);
break;
case Calendar.SATURDAY:
expListView.expandGroup(0);
break;
case Calendar.SUNDAY:
expListView.expandGroup(0);
break;
}
}
});
}
@Override
public void onLoaderReset(Loader<Cursor> arg0) {
// TODO Auto-generated method stub
}
}
ExpandableListAdapter:
package com.crevitus.timetable.adapter;
import java.util.HashMap;
import java.util.List;
import android.content.Context;
import android.graphics.Typeface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.TextView;
import com.crevitus.timetable.R;
public class ExpandableListAdapter extends BaseExpandableListAdapter {
private Context _context;
private List<String> listGroupData;
private HashMap<String, List<List<String>>> listItemData;
public ExpandableListAdapter(Context context, List<String> listGroupData,
HashMap<String, List<List<String>>> listDataChild) {
this._context = context;
this.listGroupData = listGroupData;
this.listItemData = listDataChild;
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return listItemData
.get(listGroupData
.get(groupPosition))
.get(childPosition);
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public View getChildView(int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
List<String> childText = (List<String>) getChild(groupPosition, childPosition);
if (convertView == null) {
LayoutInflater infalInflater = (LayoutInflater) this._context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = infalInflater.inflate(R.layout.exp_list_item, null);
}
TextView sTime = (TextView) convertView.findViewById(R.id.sTime);
TextView modName = (TextView) convertView.findViewById(R.id.modName);
if(childText != null)
{
modName.setText(childText.get(1) + " - Room: " + childText.get(2));
sTime.setText(childText.get(4));
}
return convertView;
}
@Override
public int getChildrenCount(int groupPosition) {
return this.listItemData.get(this.listGroupData.get(groupPosition))
.size();
}
@Override
public Object getGroup(int groupPosition) {
return this.listGroupData.get(groupPosition);
}
@Override
public int getGroupCount() {
return this.listGroupData.size();
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
String headerTitle = (String) getGroup(groupPosition);
if (convertView == null) {
LayoutInflater infalInflater = (LayoutInflater) this._context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = infalInflater.inflate(R.layout.list_group, null);
}
TextView lblListHeader = (TextView) convertView.findViewById(R.id.listGroup);
lblListHeader.setTypeface(null, Typeface.BOLD);
lblListHeader.setText(headerTitle);
return convertView;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
public void deleteChild(int groupPosition, int childPosition)
{
listItemData.get(listGroupData.get(groupPosition)).remove(childPosition);
}
}
So I'm not too familiar with the LoaderManager but I'd say the onResume() method which restarts the loaders is fishy. I'm thinking you can just remove that completely. I highly suggest reading this SO answer which discusses the difference between initLoader and restartLoader and when to use them when an Activity gets recreated. A small excerpt from there:
The Activity/Fragment life cycle has nothing to do with the decision to use one or the other method...This decision is made solely based on the "need" for a new loader. If we want to run the same query we use initLoader, if we want to run a different query we use restartLoader. We could always use restartLoader but that would be inefficient. After a screen rotation or if the user navigates away from the app and returns later to the same Activity we usually want to show the same query result and so the restartLoader would unnecessarily re-create the loader and dismiss the underlying (potentially expensive) query result.
This may add to why you are crashing as it looks like a timing issue really. You base your group count on listGroupData
which is populated in onCreate(). However you retrieve your child count from listItemData
which is not loaded until the cursor finishes. That means between onCreate() and the onLoadFinished() your listItemData
will not contain any data but your adapter thinks there's items available and hence crash with an NPE.
When writing your adapter its absolutely critical that listGroupData
reflects listItemData
. Modifying one means you need to modify the other. Also if you guarantee the List value in the map is never null, you won't need to worry about doing null checks all over the place. Example, In onCreate do this instead:
listDataChild = new HashMap<String, List<List<String>>>();
listDataChild.put("Monday", new ArrayList<List<String>>());
listDataChild.put("Tuesday", new ArrayList<List<String>>());
listDataChild.put("Wednesday", new ArrayList<List<String>>());
listDataChild.put("Thursday", new ArrayList<List<String>>());
listDataChild.put("Friday", new ArrayList<List<String>>());
listDataHeader = new ArrayList<String>(listDataChild.keySet());
This ensures every item in listDataHeader
is found in listDataChild
and that every value in listDataChild
will return an empty list until the loader finishes it and actually populates with data.