android – How to use DatePickerDialog as a Preference-ThrowExceptions

Exception or error:

I have preferences working and I am using a combination of CheckBoxPreference and EditTextPreference. I would like to replace one of them with a DatePickerDialog.

When my settings screen is showing, if you click on one of the preferences, I would like the date picker dialog to pop up for the user to select a date, and save the picked date in preferences. I have seen this work in other apps, but I cannot see how to do this.

I have the date picker dialog working from a regular view (as per tutorial), but I would to like to use it from a preference.

How to solve:

You would need to create a custom DialogPreference incorporating a DatePicker.

###

Thanks to @commonsware. I followed his project and created a date picker preference dialog. So it will help someone.

Follow the steps to open date picker in preference window.

1 . Create a custom dialog preference for date picker.

package com.packagename;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import android.content.Context;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.DatePicker;

public class DatePreference extends DialogPreference {
    private int lastDate = 0;
    private int lastMonth = 0;
    private int lastYear = 0;
    private String dateval;
    private CharSequence mSummary;
    private DatePicker picker = null;
    public static int getYear(String dateval) {
        String[] pieces = dateval.split("-");
        return (Integer.parseInt(pieces[0]));
    }

    public static int getMonth(String dateval) {
        String[] pieces = dateval.split("-");
        return (Integer.parseInt(pieces[1]));
    }

    public static int getDate(String dateval) {
        String[] pieces = dateval.split("-");
        return (Integer.parseInt(pieces[2]));
    }

    public DatePreference(Context ctxt, AttributeSet attrs) {
        super(ctxt, attrs);

        setPositiveButtonText("Set");
        setNegativeButtonText("Cancel");
    }

    @Override
    protected View onCreateDialogView() {
        picker = new DatePicker(getContext());

        // setCalendarViewShown(false) attribute is only available from API level 11
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            picker.setCalendarViewShown(false);
        }

        return (picker);
    }

    @Override
    protected void onBindDialogView(View v) {
        super.onBindDialogView(v);

        picker.updateDate(lastYear, lastMonth + 1, lastDate);
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        super.onDialogClosed(positiveResult);

        if (positiveResult) {
            lastYear = picker.getYear();
            lastMonth = picker.getMonth();
            lastDate = picker.getDayOfMonth();

            String dateval = String.valueOf(lastYear) + "-"
                    + String.valueOf(lastMonth) + "-"
                    + String.valueOf(lastDate);

            if (callChangeListener(dateval)) {
                persistString(dateval);
            }
        }
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        return (a.getString(index));
    }

    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
        dateval = null;

        if (restoreValue) {
            if (defaultValue == null) {
                Calendar cal = Calendar.getInstance();
                SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd");
                String formatted = format1.format(cal.getTime());
                dateval = getPersistedString(formatted);
            } else {
                dateval = getPersistedString(defaultValue.toString());
            }
        } else {
            dateval = defaultValue.toString();
        }
        lastYear = getYear(dateval);
        lastMonth = getMonth(dateval);
        lastDate = getDate(dateval);
    }

    public void setText(String text) {
        final boolean wasBlocking = shouldDisableDependents();

        dateval = text;

        persistString(text);

        final boolean isBlocking = shouldDisableDependents();
        if (isBlocking != wasBlocking) {
            notifyDependencyChange(isBlocking);
        }
    }

    public String getText() {
        return dateval;
    }

    public CharSequence getSummary() {
        return mSummary;
    }

    public void setSummary(CharSequence summary) {
        if (summary == null && mSummary != null || summary != null
                && !summary.equals(mSummary)) {
            mSummary = summary;
            notifyChanged();
        }
    }
}

2 . Add the following code in preference xml located in “res/xml/yourpreference.xml”

<com.packagename.DatePreference 
android:key="keyname" 
android:title="Title of the preference" 
android:defaultValue="2014-08-01" 
android:summary="Summary"/>

Note: Change the “keyname”,”Title of the preference”,”2014-08-01″,”summary” as of your requirement

3 . If you want to change the default vaules through Preference Activity use the following code.

package com.packagename;

import android.os.Bundle;
import com.packagename.DatePreference;

public class CustomPreference extends PreferenceActivity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    addPreferencesFromResource(R.xml.preferences);

    final DatePreference dp= (DatePreference) findPreference("keyname");
    dp.setText("2014-08-02");
    dp.setSummary("2014-08-02");
    dp.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
        @Override
        public boolean onPreferenceChange(Preference preference,Object newValue) {
            //your code to change values.
            dp.setSummary((String) newValue);
            return true;
        }
    });

  }
}

Now Enjoy…

###

Here‘s an implementation ready to use in your project as a lib.

To quote the source:

Use it just like any other preference in your PreferenceScreen XML:

<org.bostonandroid.datepreference.DatePreference
      android:key="dob" android:title="@string/dob"
      android:defaultValue="1991.01.01" />

###

In androidx DialogPreference class implementation is split into DialogPreference that handles data persistence, and PreferenceDialogFragmentCompat that handles UI. Building on top of Mahendran Sakkarai’s answer, this one and on EditTextPreference class as an example, it can be done like this.

1 . DatePreference class.

package com.example.util.timereminder.ui.prefs.custom;

import android.content.Context;
import android.content.res.TypedArray;
import android.text.TextUtils;
import android.util.AttributeSet;

import com.example.util.timereminder.R;

import androidx.preference.DialogPreference;

/**
 * A dialog preference that shown calendar in the dialog.
 *
 * Saves a string value.
 */
public class DatePreference extends DialogPreference {

    private String mDateValue;

    public DatePreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        return a.getString(index);
    }

    @Override
    protected void onSetInitialValue(Object defaultValue) {
        setDate(getPersistedString((String) defaultValue));
    }

    /**
     * Gets the date as a string from the current data storage.
     *
     * @return string representation of the date.
     */
    public String getDate() {
        return mDateValue;
    }

    /**
     * Saves the date as a string in the current data storage.
     *
     * @param text string representation of the date to save.
     */
    public void setDate(String text) {
        final boolean wasBlocking = shouldDisableDependents();

        mDateValue = text;

        persistString(text);

        final boolean isBlocking = shouldDisableDependents();
        if (isBlocking != wasBlocking) {
            notifyDependencyChange(isBlocking);
        }

        notifyChanged();
    }

    /**
     * A simple {@link androidx.preference.Preference.SummaryProvider} implementation for an
     * {@link DatePreference}. If no value has been set, the summary displayed will be 'Not
     * set', otherwise the summary displayed will be the value set for this preference.
     */
    public static final class SimpleSummaryProvider implements SummaryProvider<DatePreference> {

        private static SimpleSummaryProvider sSimpleSummaryProvider;

        private SimpleSummaryProvider() {}

        /**
         * Retrieve a singleton instance of this simple
         * {@link androidx.preference.Preference.SummaryProvider} implementation.
         *
         * @return a singleton instance of this simple
         * {@link androidx.preference.Preference.SummaryProvider} implementation
         */
        public static SimpleSummaryProvider getInstance() {
            if (sSimpleSummaryProvider == null) {
                sSimpleSummaryProvider = new SimpleSummaryProvider();
            }
            return sSimpleSummaryProvider;
        }

        @Override
        public CharSequence provideSummary(DatePreference preference) {
            if (TextUtils.isEmpty(preference.getDate())) {
                return (preference.getContext().getString(R.string.not_set));
            } else {
                return preference.getDate();
            }
        }
    }
}

2 . DatePreferenceDialogFragment class.

package com.example.util.timereminder.ui.prefs.custom;

import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.DatePicker;

import java.text.SimpleDateFormat;
import java.util.Calendar;

import androidx.preference.PreferenceDialogFragmentCompat;

public class DatePreferenceDialogFragment extends PreferenceDialogFragmentCompat {

    private int mLastYear;
    private int mLastMonth;
    private int mLastDay;
    private DatePicker mDatePicker;

    public static DatePreferenceDialogFragment newInstance(String key) {
        final DatePreferenceDialogFragment
                fragment = new DatePreferenceDialogFragment();
        final Bundle b = new Bundle(1);
        b.putString(ARG_KEY, key);
        fragment.setArguments(b);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String dateValue = getDatePreference().getDate();

        if (dateValue == null || dateValue.isEmpty()) {
            Calendar calendar = Calendar.getInstance();
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
            dateValue = df.format(calendar.getTime());
        }

        mLastYear = getYear(dateValue);
        mLastMonth = getMonth(dateValue);
        mLastDay = getDay(dateValue);
    }

    @Override
    protected View onCreateDialogView(Context context) {
        mDatePicker = new DatePicker(getContext());
        // Show spinner dialog for old APIs.
        mDatePicker.setCalendarViewShown(false);

        return mDatePicker;
    }

    @Override
    protected void onBindDialogView(View view) {
        super.onBindDialogView(view);

        mDatePicker.updateDate(mLastYear, mLastMonth - 1, mLastDay);
    }

    @Override
    public void onDialogClosed(boolean positiveResult) {
        if (positiveResult) {
            mLastYear = mDatePicker.getYear();
            mLastMonth = mDatePicker.getMonth() + 1;
            mLastDay = mDatePicker.getDayOfMonth();

            String dateVal = String.valueOf(mLastYear) + "-"
                    + String.valueOf(mLastMonth) + "-"
                    + String.valueOf(mLastDay);

            final DatePreference preference = getDatePreference();
            if (preference.callChangeListener(dateVal)) {
                preference.setDate(dateVal);
            }
        }
    }

    private DatePreference getDatePreference() {
        return (DatePreference) getPreference();
    }

    private int getYear(String dateString) {
        String[] datePieces = dateString.split("-");
        return (Integer.parseInt(datePieces[0]));
    }

    private int getMonth(String dateString) {
        String[] datePieces = dateString.split("-");
        return (Integer.parseInt(datePieces[1]));
    }

    private int getDay(String dateString) {
        String[] datePieces = dateString.split("-");
        return (Integer.parseInt(datePieces[2]));
    }
}

3 . In PreferenceFragment.

package com.example.util.timereminder.ui.prefs;

import android.os.Bundle;

import com.example.util.timereminder.R;
import com.example.util.timereminder.ui.prefs.custom.DatePreferenceDialogFragment;
import com.example.util.timereminder.ui.prefs.custom.DatePreference;

import androidx.fragment.app.DialogFragment;
import androidx.preference.EditTextPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup;

/**
 * Displays different preferences.
 */
public class PrefsFragment extends PreferenceFragmentCompat {

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        addPreferencesFromResource(R.xml.preferences);

        initSummary(getPreferenceScreen());
    }

    @Override
    public void onDisplayPreferenceDialog(Preference preference) {
        if (preference instanceof DatePreference) {
            final DialogFragment f;
            f = DatePreferenceDialogFragment.newInstance(preference.getKey());
            f.setTargetFragment(this, 0);
            f.show(getFragmentManager(), null);
        } else {
            super.onDisplayPreferenceDialog(preference);
        }
    }

    /**
     * Walks through all preferences.
     *
     * @param p The starting preference to search from.
     */
    private void initSummary(Preference p) {
        if (p instanceof PreferenceGroup) {
            PreferenceGroup pGrp = (PreferenceGroup) p;
            for (int i = 0; i < pGrp.getPreferenceCount(); i++) {
                initSummary(pGrp.getPreference(i));
            }
        } else {
            setPreferenceSummary(p);
        }
    }

    /**
     * Sets up summary providers for the preferences.
     *
     * @param p The preference to set up summary provider.
     */
    private void setPreferenceSummary(Preference p) {
        // No need to set up preference summaries for checkbox preferences because
        // they can be set up in xml using summaryOff and summary On
        if (p instanceof DatePreference) {
            p.setSummaryProvider(DatePreference.SimpleSummaryProvider.getInstance());
        } else if (p instanceof EditTextPreference) {
            p.setSummaryProvider(EditTextPreference.SimpleSummaryProvider.getInstance());
        }
    }
}

4 . And in preference.xml. If default value left out, calendar opens on the current date.

<com.example.util.timereminder.ui.prefs.custom.DatePreference
    android:title="@string/prefs_date_of_birth_title"
    android:key="@string/prefs_date_of_birth_key"
    android:defaultValue="2014-08-01"
    app:iconSpaceReserved="false"/>

###

Just a simple way to use TimePickerFragment in the settings, it doesn’t really answer your question, but it can help some guys.
Please read this before: https://developer.android.com/guide/topics/ui/settings
From “Overview” to “Preference… attributes” of course… Oo

controller.fragments

public class TimePickerFragment extends DialogFragment {

    private TimePickerDialog.OnTimeSetListener onTimeSetListener;
    private int hours;
    private int minutes;

    TimePickerFragment(TimePickerDialog.OnTimeSetListener onTimeSetListener, int hours, int minutes) {
        this.onTimeSetListener = onTimeSetListener;
        this.hours = hours;
        this.minutes = minutes;
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        return new TimePickerDialog(getActivity(), R.style.dateTimePicker,
                onTimeSetListener, hours, minutes, DateFormat.is24HourFormat(getActivity()));
    }
}

res.values.style

<style name="dateTimePicker" parent="ThemeOverlay.MaterialComponents.Dialog">
        <item name="colorAccent">@color/colorPrimary</item>
</style>

res.xml.root_preferences.xml create the xml folder in res & the file root_preferences of course Oo

<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
    <PreferenceCategory app:title="Set time">

        <Preference
            app:key="set_time"
            app:title="Set time"
            app:summary="bla bla bla"/>

    </PreferenceCategory>
</PreferenceScreen>

controller.activities.SettingsActivity

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.settings_activity);
    getSupportFragmentManager()
            .beginTransaction()
            .add(R.id.settings, new SettingsFragment())
            .commit();
}

controller.fragments.SettingsFragment

public class SettingsFragment extends PreferenceFragmentCompat implements TimePickerDialog.OnTimeSetListener {


         private Preference setTime;

         public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
            setPreferencesFromResource(R.xml.root_preferences, rootKey);
            getPreference();
            configListener();
        }

        private void getPreference() {
            setTime = findPreference("set_time");
        }

        private void configListener() {
            if (setTime != null){
                setTime.setOnPreferenceClickListener(preference -> {
                    showTimeDialog(preference);
                    return true;
                });
            }
        }

        private void showTimeDialog(Preference preference) {
            String value = preference.getSharedPreferences().getString("set_time", "12:00");
            String[] time = value.split(":");
            int hours = Integer.parseInt(time[0]);
            int minutes = Integer.parseInt(time[1]);
            if (getFragmentManager() != null) {
            new TimePickerFragment(this, hours, minutes)
                .show(getFragmentManager(), getString(R.string.tag_time_picker));
            }
        }

        @Override
        public void onTimeSet(TimePicker timePicker, int h, int m) {
            String time = format(Locale.getDefault(),"%02d", h) + ":" + format(Locale.getDefault(), "%02d", m);
            SharedPreferences sharedPreferences =
                    PreferenceManager.getDefaultSharedPreferences(context);
            sharedPreferences.edit().putString("set_time", time).apply();
            // if you use setOnPreferenceChangeListener on it, use setTime.callChangeListener(time);
        }
}

I didn’t document anything because after reading the guide, you should all understand ^^

###

While looking for a TimePicker to use in preferences I found this thread.
I’d like to point out that there is also a TimePicker project in that repo (link).

The only problem is that the default.jardesc is not available, but can easily be made from the corresponding one in the DatePicker project.

Leave a Reply

Your email address will not be published. Required fields are marked *