I was using an unofficial PreferenceFragment compatibility layer for Android 1.6 and up. But Google has released v7 Preference Support Library. So my idea is to migrate project library.
CURRENT IMPLEMENTATION: In the project I have created a custom setting to support dates.
public class DatePreference extends DialogPreference implements DatePicker.OnDateChangedListener {
private String dateString;
private String changedValueCanBeNull;
private DatePicker datePicker;
private Calendar maxCalendar;
private int maxYear;
private int maxMonth;
private int maxDay;
public DatePreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
protected View onCreateDialogView() {
this.datePicker = new DatePicker(getContext());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
this.datePicker.setMaxDate(Calendar.getInstance().getTimeInMillis());
} else {
maxCalendar = Calendar.getInstance();
maxYear = maxCalendar.get(Calendar.YEAR);
maxMonth = maxCalendar.get(Calendar.MONTH);
maxDay = maxCalendar.get(Calendar.DAY_OF_MONTH);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
this.datePicker.setCalendarViewShown(false);
}
final Calendar calendar = getDate();
datePicker.init(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH),
this);
return datePicker;
}
public Calendar getDate() {
try {
final SimpleDateFormat sfd = formatter();
final Date date = sfd.parse(defaultValue());
final Calendar cal = Calendar.getInstance();
cal.setTime(date);
return cal;
} catch (ParseException e) {
return defaultCalendar();
}
}
public void setDate(final String dateString) {
this.dateString = dateString;
}
public static SimpleDateFormat formatter() {
return new SimpleDateFormat("yyyy.MM.dd", Locale.getDefault());
}
public static SimpleDateFormat summaryFormatter() {
return new SimpleDateFormat("MMMM dd, yyyy", Locale.getDefault());
}
@Override
protected Object onGetDefaultValue(final TypedArray a, final int index) {
return a.getString(index);
}
@Override
protected void onSetInitialValue(final boolean restoreValue, final Object def) {
if (restoreValue) {
this.dateString = getPersistedString(defaultValue());
setTheDate(this.dateString);
} else {
final boolean wasNull = this.dateString == null;
setDate((String) def);
if (!wasNull) {
persistDate(this.dateString);
}
}
}
@Override
protected Parcelable onSaveInstanceState() {
if (isPersistent()) {
return super.onSaveInstanceState();
} else {
return new SavedState(super.onSaveInstanceState());
}
}
@Override
protected void onRestoreInstanceState(final Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {
super.onRestoreInstanceState(state);
try {
if (state != null) {
setTheDate(((SavedState) state).dateValue);
}
} catch (ClassCastException e) {
SiempreListoLogger.INSTANCE.error(DatePreference.class.getSimpleName(), "Error casting class to date preference " + Log.getStackTraceString(e));
}
} else {
final SavedState s = (SavedState) state;
super.onRestoreInstanceState(s.getSuperState());
setTheDate(s.dateValue);
}
}
@Override
public void onDateChanged(final DatePicker view, final int year, final int month, final int day) {
Calendar selected = new GregorianCalendar(year, month, day);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB && selected.after(maxCalendar)) {
selected = new GregorianCalendar(maxYear, maxMonth, maxDay);
}
this.changedValueCanBeNull = formatter().format(selected.getTime());
}
@Override
protected void onDialogClosed(final boolean shouldSave) {
if (shouldSave && this.changedValueCanBeNull != null) {
setTheDate(this.changedValueCanBeNull);
this.changedValueCanBeNull = null;
}
}
private void setTheDate(final String s) {
setDate(s);
persistDate(s);
}
private void persistDate(final String s) {
persistString(s);
setSummary(summaryFormatter().format(getDate().getTime()));
}
public static Calendar defaultCalendar() {
return new GregorianCalendar(1900, 0, 1);
}
public static String defaultCalendarString() {
return formatter().format(defaultCalendar().getTime());
}
private String defaultValue() {
if (this.dateString == null) {
setDate(defaultCalendarString());
}
return this.dateString;
}
@Override
public void onClick(final DialogInterface dialog, final int which) {
super.onClick(dialog, which);
datePicker.clearFocus();
onDateChanged(datePicker, datePicker.getYear(), datePicker.getMonth(), datePicker.getDayOfMonth());
onDialogClosed(which == DialogInterface.BUTTON_POSITIVE); // OK?
}
private static class SavedState extends BaseSavedState {
private transient String dateValue;
@SuppressWarnings("unused")
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(final Parcel parcel) {
return new SavedState(parcel);
}
@Override
public SavedState[] newArray(final int size) {
return new SavedState[size];
}
};
public SavedState(final Parcel p) {
super(p);
dateValue = p.readString();
}
public SavedState(final Parcelable p) {
super(p);
}
@Override
public void writeToParcel(final Parcel parcel, final int flags) {
super.writeToParcel(parcel, flags);
parcel.writeString(dateValue);
}
}
}
And to support this preference I'm using a DatePickerFragment to allow user select the date using native DatePicker.
public class DatePickerFragment extends DialogFragment {
public static final String TAG = DatePickerFragment.class.getName();
private static final String MINIMUM_DATE_TO_SHOW = "minimum_date_to_show";
private static final String DATE_TO_SHOW = "date_to_show";
private DatePickerDialog.OnDateSetListener mListener;
public static DatePickerFragment newInstance(DatePickerDialog.OnDateSetListener listener) {
DatePickerFragment newInstance = new DatePickerFragment();
newInstance.mListener = listener;
return newInstance;
}
public static DatePickerFragment newInstance(DatePickerDialog.OnDateSetListener listener, Date date) {
DatePickerFragment newInstance = new DatePickerFragment();
Bundle args = new Bundle();
args.putSerializable(MINIMUM_DATE_TO_SHOW, date);
args.putSerializable(DATE_TO_SHOW, date);
newInstance.setArguments(args);
newInstance.mListener = listener;
return newInstance;
}
@Override
@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the current date as the default date in the picker
final Calendar c = Calendar.getInstance();
int minimumYear = c.get(Calendar.YEAR);
int minimumMonth = c.get(Calendar.MONTH);
int minimumDay = c.get(Calendar.DAY_OF_MONTH);
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
if (getArguments() != null && getArguments().containsKey(MINIMUM_DATE_TO_SHOW)) {
Date date = (Date) getArguments().getSerializable(MINIMUM_DATE_TO_SHOW);
if (date != null) {
c.setTime(date);
minimumYear = c.get(Calendar.YEAR);
minimumMonth = c.get(Calendar.MONTH);
minimumDay = c.get(Calendar.DAY_OF_MONTH);
}
}
if (getArguments() != null && getArguments().containsKey(DATE_TO_SHOW)) {
Date date = (Date) getArguments().getSerializable(DATE_TO_SHOW);
if (date != null) {
c.setTime(date);
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH);
day = c.get(Calendar.DAY_OF_MONTH);
}
}
final Date minimumDate = Utils.getDateFromValues(minimumYear, minimumMonth, minimumDay);
// Create a new instance of DatePickerDialog and return it
final DatePickerDialog datePickerDialog = new DatePickerDialog(getActivity(), mListener, year, month, day);
datePickerDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(android.R.string.ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
DatePicker datePicker = datePickerDialog.getDatePicker();
datePicker.setMinDate(minimumDate.getTime());
mListener.onDateSet(datePicker, datePicker.getYear(), datePicker.getMonth(), datePicker.getDayOfMonth());
}
});
datePickerDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getString(android.R.string.cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
DatePickerFragment.this.dismiss();
}
});
return datePickerDialog;
}
}
And the settings.xml:
<PreferenceCategory
android:layout="@layout/preference_activity_bg"
android:title="@string/pref_user_private_info">
<com.company.project.util.DatePreference
android:defaultValue="1980.01.01"
android:icon="@drawable/ic_birthdate"
android:key="prefBirthdate"
android:title="@string/pref_user_birthdate"/>
<Preference
android:icon="@drawable/ic_email"
android:key="prefEmail"
android:summary="@string/pref_user_email"
android:title="@string/pref_user_email"/>
<EditTextPreference
android:icon="@drawable/ic_ic_telefono"
android:inputType="phone"
android:key="prefTel"
android:maxLength="10"
android:summary="@string/pref_user_phone"
android:title="@string/pref_user_phone"/>
<Preference
android:icon="@drawable/ic_points"
android:key="prefBalance"
android:title="@string/pref_user_points"/>
</PreferenceCategory>
PROBLEM: The problem states is that onCreateDialogView and onDialogClosed are not found in v7 support DialogPreference.
EDIT: I found that v7 PreferenceDialogFragmentCompat should be used with v7 DialogPreference.
The v7 PreferenceDialogFragmentCompat should be used in conjunction with v7 DialogPreference. So using this response code could be refactored on this way.
Modified DatePrefence class:
public class DatePreference extends DialogPreference {
private final String TAG = getClass().getSimpleName();
public String dateString;
public DatePreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public static String dateToString(Calendar calendar) {
return summaryFormatter().format(calendar.getTime());
}
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getString(index);
}
@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
if (restorePersistedValue) {
if (defaultValue == null) dateString = getPersistedString(defaultValue());
else dateString = getPersistedString(defaultValue.toString());
setTheDate(dateString);
} else {
final boolean wasNull = dateString == null;
setDate((String) defaultValue);
if (!wasNull) {
persistStringValue(dateString);
}
}
}
@Override
protected Parcelable onSaveInstanceState() {
if (isPersistent()) {
return super.onSaveInstanceState();
} else {
return new SavedState(super.onSaveInstanceState());
}
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {
super.onRestoreInstanceState(state);
try {
if (state != null) {
setTheDate(((SavedState) state).dateValue);
}
} catch (ClassCastException e) {
SiempreListoLogger.INSTANCE.error(TAG, "Error casting class to date preference " + Log.getStackTraceString(e));
}
} else {
SavedState s = (SavedState) state;
super.onRestoreInstanceState(s.getSuperState());
setTheDate(s.dateValue);
}
}
private String defaultValue() {
if (this.dateString == null) {
setDate(defaultCalendarString());
}
return this.dateString;
}
public static String defaultCalendarString() {
return formatter().format(defaultCalendar().getTime());
}
public static SimpleDateFormat formatter() {
return new SimpleDateFormat("yyyy.MM.dd", Locale.getDefault());
}
public static SimpleDateFormat summaryFormatter() {
return new SimpleDateFormat("MMMM dd, yyyy", Locale.getDefault());
}
public static Calendar defaultCalendar() {
return new GregorianCalendar(1900, 0, 1);
}
public void setTheDate(String s) {
setDate(s);
persistStringValue(s);
}
public void persistStringValue(String value) {
persistString(value);
setSummary(summaryFormatter().format(getDate().getTime()));
}
public void setDate(String dateString) {
this.dateString = dateString;
}
public Calendar getDate() {
try {
final SimpleDateFormat sfd = formatter();
final Date date = sfd.parse(defaultValue());
final Calendar cal = Calendar.getInstance();
cal.setTime(date);
return cal;
} catch (ParseException e) {
return defaultCalendar();
}
}
private static class SavedState extends Preference.BaseSavedState {
private transient String dateValue;
@SuppressWarnings("unused")
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(final Parcel parcel) {
return new SavedState(parcel);
}
@Override
public SavedState[] newArray(final int size) {
return new SavedState[size];
}
};
public SavedState(final Parcel p) {
super(p);
dateValue = p.readString();
}
public SavedState(final Parcelable p) {
super(p);
}
@Override
public void writeToParcel(final Parcel parcel, final int flags) {
super.writeToParcel(parcel, flags);
parcel.writeString(dateValue);
}
}
}
New DatePreferenceDialogFragmentCompat class:
public class DatePreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat implements DialogPreference.TargetFragment, DatePicker.OnDateChangedListener {
DatePicker datePicker = null;
private String changedValueCanBeNull;
private Calendar maxCalendar;
private int maxYear;
private int maxMonth;
private int maxDay;
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
protected View onCreateDialogView(Context context) {
datePicker = new DatePicker(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
datePicker.setMaxDate(Calendar.getInstance().getTimeInMillis());
} else {
maxCalendar = Calendar.getInstance();
maxYear = maxCalendar.get(Calendar.YEAR);
maxMonth = maxCalendar.get(Calendar.MONTH);
maxDay = maxCalendar.get(Calendar.DAY_OF_MONTH);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
this.datePicker.setCalendarViewShown(false);
}
DatePreference pref = (DatePreference) getPreference();
Calendar calendar = pref.getDate();
datePicker.init(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), this);
return datePicker;
}
@Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
DatePreference pref = (DatePreference) getPreference();
String value = DatePreference.dateToString(pref.getDate());
if (pref.callChangeListener(value)) pref.persistStringValue(value);
}
@Override
public void onDialogClosed(boolean positiveResult) {
if (positiveResult && changedValueCanBeNull != null) {
DatePreference pref = (DatePreference) getPreference();
pref.setTheDate(changedValueCanBeNull);
changedValueCanBeNull = null;
}
}
@Override
public void onClick(DialogInterface dialog, int which) {
super.onClick(dialog, which);
datePicker.clearFocus();
onDateChanged(datePicker, datePicker.getYear(), datePicker.getMonth(), datePicker.getDayOfMonth());
onDialogClosed(which == DialogInterface.BUTTON_POSITIVE); // OK?
}
@Override
public Preference findPreference(CharSequence charSequence) {
return getPreference();
}
@Override
public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
Calendar selected = new GregorianCalendar(year, monthOfYear, dayOfMonth);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB && selected.after(maxCalendar)) {
selected = new GregorianCalendar(maxYear, maxMonth, maxDay);
}
changedValueCanBeNull = DatePreference.formatter().format(selected.getTime());
}
}
I also added the following code to my SettingsFragment that extends PreferenceFragmentCompat:
@Override
public void onDisplayPreferenceDialog(Preference preference) {
DialogFragment dialogFragment = null;
if (preference instanceof DatePreference) {
dialogFragment = new DatePreferenceDialogFragmentCompat();
Bundle bundle = new Bundle(1);
bundle.putString("key", preference.getKey());
dialogFragment.setArguments(bundle);
}
if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0);
dialogFragment.show(this.getFragmentManager(), "android.support.v7.preference.PreferenceFragment.DIALOG");
} else {
super.onDisplayPreferenceDialog(preference);
}
}