Hard Challenge - Null Pointer Exception for date on dialogs


#1

My app crashes after choosing either the positive or negative button for my ChoiceDialog dialog fragment. Keeps saying null pointer exception for this line in all 3 dialog fragments: ChoiceFragment, DatePickerFragment, and TimePickerFragment.

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

mDate = (CiDate)getArguments().getSerializable(EXTRA_DATE); // null pointer exception at this line

Here is the code for my ChoiceFragment:

[code]package com.bignerdranch.android.criminalintent;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;

public class ChoiceFragment extends DialogFragment {

private CiDate mDate;

public static final String EXTRA_DATE =
		"com.bignerdranch.android.criminalintent.date";

// DatePickerFragment's tag:
private static final String DIALOG_DATE = "date";
	
// TimePickerFragment's tag:
private static final String DIALOG_TIME = "time";

private static final int REQUEST_DATE = 0;
private static final int REQUEST_TIME = 1;

public static ChoiceFragment newInstance(CiDate date) {
	Bundle args = new Bundle();
	args.putSerializable(EXTRA_DATE, date);
	
	ChoiceFragment fragment = new ChoiceFragment();
	fragment.setArguments(args);
	
	return fragment;
}

// Method used to call back to the target fragment
private void sendResult(int resultCode) {
	if (getTargetFragment() == null)
		return;
	
	Intent i = new Intent();
	i.putExtra(EXTRA_DATE, mDate);
	
	getTargetFragment()
		.onActivityResult(getTargetRequestCode(), resultCode, i);
}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
	
	mDate = (CiDate)getArguments().getSerializable(EXTRA_DATE);
	
	return new AlertDialog.Builder(getActivity())
		.setTitle(R.string.choice_dialog_title)
		.setPositiveButton(R.string.choice_positive_button_text, 
				new DialogInterface.OnClickListener() {
					
					@Override
					public void onClick(DialogInterface dialog, int which) {
						FragmentManager fm = getActivity()
								.getSupportFragmentManager();
						DatePickerFragment date_dialog = new DatePickerFragment();
						date_dialog.setTargetFragment(getTargetFragment(), REQUEST_DATE);
						date_dialog.show(fm,  DIALOG_DATE);				
					
						sendResult(Activity.RESULT_OK);							
					}
				})
		.setNegativeButton(R.string.choice_negative_button_text, 
				new DialogInterface.OnClickListener() {
			
					@Override
					public void onClick(DialogInterface dialog, int which) {
						FragmentManager fm = getActivity()
								.getSupportFragmentManager();
						TimePickerFragment time_dialog = new TimePickerFragment();
						time_dialog.setTargetFragment(getTargetFragment(), REQUEST_TIME);
						time_dialog.show(fm,  DIALOG_TIME);				
					
						sendResult(Activity.RESULT_CANCELED);					
					}
		})
		.create();
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
	
	if (resultCode != Activity.RESULT_OK) return;
	
	if (requestCode == REQUEST_DATE) {			
		mDate = (CiDate)data
				.getSerializableExtra(DatePickerFragment.EXTRA_DATE);			
	}
	
	if (requestCode == REQUEST_TIME) {
		mDate = (CiDate)data
				.getSerializableExtra(TimePickerFragment.EXTRA_DATE);
	}

}

}
[/code]

Here is the Customized Crime class:

[code]package com.bignerdranch.android.criminalintent;

import java.util.Date;
import java.util.UUID;

public class Crime {

private UUID mId;
private String mTitle;
private CiDate mDate;
private boolean mSolved;
//private String mFormattedDate;

public Crime() {
	// Generate unique identifier
	mId = UUID.randomUUID();
	mDate = new CiDate();
}

public String getTitle() {
	return mTitle;
}

public void setTitle(String title) {
	mTitle = title;
}

public UUID getId() {
	return mId;
}

/***************************************************************************
public CiDate getDate() {
//mDate.applyPattern(“EEEE, MMM dd, yyyy”);
//mDate = new SimpleDateFormat(“EEEE, MMM dd, yyyy”);
//mFormattedDate = mDate.format(new Date());
return mDate;
}

public void setDate(Date date) {
	mDate = new CiDate(date);
}

***************************************************************************/

public Date getDate() {
	return mDate.getDate();
}

public String getStringDate() {
	return (String)mDate.getFormattedDate("EEEE, MMM dd, yyyy");
}

public CiDate getCiDate() {
	return mDate;
}


public void setDate(CiDate date) {
	mDate = date;
}


public boolean isSolved() {
	return mSolved;
}

public void setSolved(boolean solved) {
	mSolved = solved;
}

@Override
public String toString() {
	return mTitle;
}

}
[/code]

Here’s my edited version of someone else’s (name i can’t remember) custom CiDate class:

[code]package com.bignerdranch.android.criminalintent;

import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import android.text.format.DateFormat;

public class CiDate implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private int mYear;
private int mMonth;
private int mDay;
private int mHour;
private int mMinute;

public CiDate() {
//Calendar calendar = Calendar.getInstance();
this(Calendar.getInstance().getTimeInMillis());
}

public CiDate(int year, int month, int day, int hour, int minute) {
mYear = year;
mMonth = month;
mDay = day;
mHour = hour;
mMinute = minute;
}

public CiDate(long milliseconds) {
Date date = new Date(milliseconds);
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
mYear = calendar.get(Calendar.YEAR);
mMonth = calendar.get(Calendar.MONTH);
mDay = calendar.get(Calendar.DAY_OF_MONTH);
mHour = calendar.get(Calendar.HOUR_OF_DAY);
mMinute = calendar.get(Calendar.MINUTE);
}

public long getMilliseconds() {
GregorianCalendar calendar = new GregorianCalendar(mYear, mMonth, mDay, mHour, mMinute);
return calendar.getTimeInMillis();
}

public CharSequence getFormattedDate(String formatString) {
return DateFormat.format(formatString, new GregorianCalendar(mYear, mMonth, mDay, mHour, mMinute).getTime());
}

public Date getDate() {
return new GregorianCalendar(mYear, mMonth, mDay, mHour, mMinute).getTime();
}

public int getYear() {
return mYear;
}

public void setYear(int year) {
mYear = year;
}

public int getMonth() {
return mMonth;
}

public void setMonth(int month) {
mMonth = month;
}

public int getDay() {
return mDay;
}

public void setDay(int day) {
mDay = day;
}

public int getHour() {
return mHour;
}

public void setHour(int hour) {
mHour = hour;
}

public int getMinute() {
return mMinute;
}

public void setMinute(int minute) {
mMinute = minute;
}

}
[/code]

Here are the Date and Time picker fragment contents:
DatePickerFragment.java

[code]package com.bignerdranch.android.criminalintent;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.View;
import android.widget.DatePicker;
import android.widget.DatePicker.OnDateChangedListener;

public class DatePickerFragment extends DialogFragment {

public static final String EXTRA_DATE =
		"com.bignerdranch.android.criminalintent.date";

private CiDate mDate;

public static DatePickerFragment newInstance(CiDate date) {
	Bundle args = new Bundle();
	args.putSerializable(EXTRA_DATE, date);
	
	DatePickerFragment fragment = new DatePickerFragment();
	fragment.setArguments(args);
	
	return fragment;
}

// Method used to call back to the target fragment
private void sendResult(int resultCode) {
	if (getTargetFragment() == null)
		return;
	
	Intent i = new Intent();
	i.putExtra(EXTRA_DATE, mDate);
	
	getTargetFragment()
		.onActivityResult(getTargetRequestCode(), resultCode, i);
}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
	
	mDate = (CiDate)getArguments().getSerializable(EXTRA_DATE);
			
	int year = mDate.getYear();
	int month = mDate.getMonth();
	int day = mDate.getDay();
	
	View v = getActivity().getLayoutInflater()
			.inflate(R.layout.dialog_date, null);
	
	DatePicker datePicker = (DatePicker)v.findViewById(R.id.dialog_date_datePicker);
	datePicker.init(year, month,  day, new OnDateChangedListener() {
		public void onDateChanged(DatePicker view, int year, int month, int day) {
			
			mDate.setYear(year);
			mDate.setMonth(month);
			mDate.setDay(day);
			
			// Update argument to preserve selected value on rotation
			getArguments().putSerializable(EXTRA_DATE, mDate);
		}
	});
	
	return new AlertDialog.Builder(getActivity())
		.setView(v)
		.setTitle(R.string.date_picker_title)
		.setPositiveButton(android.R.string.ok, 
				new DialogInterface.OnClickListener() {
					
					@Override
					public void onClick(DialogInterface dialog, int which) {
						sendResult(Activity.RESULT_OK);
						
					}
				})
		.create();
}

}
[/code]

and TimePickerFragment.java:

[code]package com.bignerdranch.android.criminalintent;

import java.util.Calendar;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.View;
import android.widget.TimePicker;

public class TimePickerFragment extends DialogFragment {
public static final String EXTRA_DATE =
“com.bignerdranch.android.criminalintent.date”;

private CiDate mDate;

public static TimePickerFragment newInstance(CiDate date) {
	Bundle args = new Bundle();
	args.putSerializable(EXTRA_DATE, date);
	
	TimePickerFragment fragment = new TimePickerFragment();
	fragment.setArguments(args);
	
	return fragment;
}

// Method used to call back to the target fragment
private void sendResult(int resultCode) {
	if (getTargetFragment() == null)
		return;
	
	Intent i = new Intent();
	i.putExtra(EXTRA_DATE, mDate);
	
	getTargetFragment()
		.onActivityResult(getTargetRequestCode(), resultCode, i);
}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
	
	mDate = (CiDate)getArguments().getSerializable(EXTRA_DATE);		
	
	@SuppressWarnings("unused")
	int hour = mDate.getHour();
	@SuppressWarnings("unused")
	int minute = mDate.getMinute();
			
	View v = getActivity().getLayoutInflater()
			.inflate(R.layout.dialog_time, null);
	
	TimePicker timePicker = (TimePicker)v.findViewById(R.id.dialog_time_timePicker);
	timePicker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {
		public void onTimeChanged(TimePicker view, int hour, int minute) {
			
			mDate.setHour(hour);
			mDate.setMinute(minute);
							
			// Update argument to preserve selected value on rotation
			getArguments().putSerializable(EXTRA_DATE, mDate);
		}
	});
	
	return new AlertDialog.Builder(getActivity())
		.setView(v)
		.setTitle(R.string.time_picker_title)
		.setPositiveButton(android.R.string.ok, 
				new DialogInterface.OnClickListener() {
					
					@Override
					public void onClick(DialogInterface dialog, int which) {
						sendResult(Activity.RESULT_OK);							
					}
				})
		.create();
}

}
[/code]

Here is the parent fragment that launches the ChoiceFragment Dialog:
CrimeFragment.java

[code]package com.bignerdranch.android.criminalintent;

import java.util.UUID;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;

public class CrimeFragment extends Fragment {
// Index key/value pair for savedInstanceState bundle
public static final String EXTRA_CRIME_ID =
“com.bignerdranch.android.criminalintent.crime_id”;

// DatePickerFragment's tag:
//private static final String DIALOG_DATE = "date";

// TimePickerFragment's tag:
//private static final String DIALOG_TIME = "time";

// ChoiceFragment's tag:
private static final String DIALOG_CHOICE = "choice";

// request codes for target fragments *used to pass date from child fragments to the parent crimefragment	
//private static final int REQUEST_DATE = 0;
//private static final int REQUEST_TIME = 1;
private static final int REQUEST_USER_CHOICE = 2;

private Crime mCrime;
private EditText mTitleField;
private Button mDateButton;
private CheckBox mSolvedCheckBox;


// Method to create a new instance of crimeFragment with the passed in crimeId 
//       then bundle that crimeId to that crime fragment object.
public static CrimeFragment newInstance(UUID crimeId) {
	Bundle args = new Bundle();
	args.putSerializable(EXTRA_CRIME_ID, crimeId);
	
	CrimeFragment fragment = new CrimeFragment();
	fragment.setArguments(args);
	
	return fragment;
}

@Override
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	//UUID crimeId = (UUID)getActivity().getIntent()
		//	.getSerializableExtra(EXTRA_CRIME_ID);
	UUID crimeId = (UUID)getArguments().getSerializable(EXTRA_CRIME_ID);
	mCrime = CrimeLab.get(getActivity()).getCrime(crimeId);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
	
	if (resultCode != Activity.RESULT_OK) return;
	
	if (resultCode == Activity.RESULT_OK) {			
	
		CiDate date = (CiDate)data
				.getSerializableExtra(ChoiceFragment.EXTRA_DATE);
		mCrime.setDate(date);
		
		updateDate();
	}

}

public void updateDate() {
	mDateButton.setText(mCrime.getStringDate());
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
	// Explicitly inflate the fragment layout as a view object
	View v = inflater.inflate(R.layout.fragment_crime, parent, false);
	
	// Use this view object to assign a reference of the EditText to a variable
	mTitleField = (EditText)v.findViewById(R.id.crime_title);
	mTitleField.setText(mCrime.getTitle());
	mTitleField.addTextChangedListener(new TextWatcher() {
		public void onTextChanged(CharSequence c, int start, int before, int count) {
			mCrime.setTitle(c.toString());
		}
		
		public void beforeTextChanged(CharSequence c, int start, int count, int after) {
			// This space intentionally left blank
		}
		
		public void afterTextChanged(Editable c) {
			// This one too
		}
	});		
					
	mDateButton = (Button)v.findViewById(R.id.crime_date);
	
	updateDate();
	
	// click listener for date button that starts the alert dialog / date picker
	mDateButton.setOnClickListener(new View.OnClickListener() {
		
		@Override
		public void onClick(View v) {
			FragmentManager fm = getActivity()
					.getSupportFragmentManager();
			//DatePickerFragment dialog = new DatePickerFragment();
			ChoiceFragment dialog = ChoiceFragment.newInstance(mCrime.getCiDate());
			dialog.setTargetFragment(CrimeFragment.this, REQUEST_USER_CHOICE);
			dialog.show(fm,  DIALOG_CHOICE);				
		}
	});
	
	mSolvedCheckBox = (CheckBox)v.findViewById(R.id.crime_solved);
	mSolvedCheckBox.setChecked(mCrime.isSolved());
	mSolvedCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
		public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
			//Set the crime's solved property
			mCrime.setSolved(isChecked);
		}
	});
	
	return v;
}

}
[/code]

I have been trying to fix this problem for days now. Please help.


#2

You’ve got me baffled - I can’t see anything wrong, either. Can you show the stack trace you’re getting here?