TimePicker in PreferenceScreen

AndroidAndroid Preferences

Android Problem Overview


I'd like to create a preference field called Interval and I want to be able to popup a TimePicker and set a mm:ss formated value with minimal value 00:30 and step 30 seconds.

Is it possible to use TimePicker in PreferenceScreen ?

Android Solutions


Solution 1 - Android

There is no TimePreference built into Android. However, creating your own is fairly easy. Here's one I did:

import android.content.Context;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TimePicker;

public class TimePreference extends DialogPreference {
	private int lastHour=0;
	private int lastMinute=0;
	private TimePicker picker=null;

	public static int getHour(String time) {
		String[] pieces=time.split(":");

		return(Integer.parseInt(pieces[0]));
	}

	public static int getMinute(String time) {
		String[] pieces=time.split(":");

		return(Integer.parseInt(pieces[1]));
	}

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

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

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

		return(picker);
	}

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

		picker.setCurrentHour(lastHour);
		picker.setCurrentMinute(lastMinute);
	}

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

		if (positiveResult) {
			lastHour=picker.getCurrentHour();
			lastMinute=picker.getCurrentMinute();

			String time=String.valueOf(lastHour)+":"+String.valueOf(lastMinute);

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

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

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

		if (restoreValue) {
			if (defaultValue==null) {
				time=getPersistedString("00:00");
			}
			else {
				time=getPersistedString(defaultValue.toString());
			}
		}
		else {
			time=defaultValue.toString();
		}

		lastHour=getHour(time);
		lastMinute=getMinute(time);
	}
}

Solution 2 - Android

I have modified the code from first answer:

  • it stores selected time in long form (milliseconds) which is easier to work with (using Calendar) then string
  • it automatically shows selected time in summary field in user's format (12 or 24 hour)

Updated code:

public class TimePreference extends DialogPreference {
	private Calendar calendar;
	private TimePicker picker = null;

	public TimePreference(Context ctxt) {
		this(ctxt, null);
	}

	public TimePreference(Context ctxt, AttributeSet attrs) {
		this(ctxt, attrs, android.R.attr.dialogPreferenceStyle);
	}

	public TimePreference(Context ctxt, AttributeSet attrs, int defStyle) {
		super(ctxt, attrs, defStyle);

		setPositiveButtonText(R.string.set);
		setNegativeButtonText(R.string.cancel);
		calendar = new GregorianCalendar();
	}

	@Override
	protected View onCreateDialogView() {
		picker = new TimePicker(getContext());
		return (picker);
	}

	@Override
	protected void onBindDialogView(View v) {
		super.onBindDialogView(v);
		picker.setCurrentHour(calendar.get(Calendar.HOUR_OF_DAY));
		picker.setCurrentMinute(calendar.get(Calendar.MINUTE));
	}

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

		if (positiveResult) {
			calendar.set(Calendar.HOUR_OF_DAY, picker.getCurrentHour());
			calendar.set(Calendar.MINUTE, picker.getCurrentMinute());

			setSummary(getSummary());
			if (callChangeListener(calendar.getTimeInMillis())) {
				persistLong(calendar.getTimeInMillis());
				notifyChanged();
			}
		}
	}

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

	@Override
	protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {

		if (restoreValue) {
			if (defaultValue == null) {
				calendar.setTimeInMillis(getPersistedLong(System.currentTimeMillis()));
			} else {
				calendar.setTimeInMillis(Long.parseLong(getPersistedString((String) defaultValue)));
			}
		} else {
			if (defaultValue == null) {
				calendar.setTimeInMillis(System.currentTimeMillis());
			} else {
				calendar.setTimeInMillis(Long.parseLong((String) defaultValue));
			}
		}
		setSummary(getSummary());
	}

	@Override
	public CharSequence getSummary() {
		if (calendar == null) {
			return null;
		}
		return DateFormat.getTimeFormat(getContext()).format(new Date(calendar.getTimeInMillis()));
	}
} 

Solution 3 - Android

For those whom the implementation of a custom Preference isn't so obvious (like it wasn't for me), you have to add this to your preferences.xml or whatever you're calling it.

You'll end up with something like this:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >

    <EditTextPreference
        android:key="editTextPref_Key"
        android:title="@string/editTextPref_title"/>
    <com.example.myapp.TimePreference
        android:key="timePrefA_Key"
        android:title="@string/timePrefA_title"/>    
    <com.example.myapp.TimePreference
        android:key="timePrefB_Key"
        android:title="@string/timePrefB_title"/>

</PreferenceScreen>

Assuming you added the TimePreference to your own root package:
(src/com/example/myapp/TimePreference.java)

Solution 4 - Android

For Preferences Support Library different code is needed. It requires two custom classes TimePreference and TimePreferenceDialogFragmentCompat, as well as overide of onDisplayPreferenceDialog method in PreferenceFragmentCompat extension class.


TimePreference.java

package com.test;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.v7.preference.DialogPreference;
import android.util.AttributeSet;

public class TimePreference extends DialogPreference
{
    public int hour = 0;
    public int minute = 0;

    public static int parseHour(String value)
    {
        try
        {
            String[] time = value.split(":");
            return (Integer.parseInt(time[0]));
        }
        catch (Exception e)
        {
            return 0;
        }
    }

    public static int parseMinute(String value)
    {
        try
        {
            String[] time = value.split(":");
            return (Integer.parseInt(time[1]));
        }
        catch (Exception e)
        {
            return 0;
        }
    }

    public static String timeToString(int h, int m)
    {
        return String.format("%02d", h) + ":" + String.format("%02d", m);
    }

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

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

    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue)
    {
        String value;
        if (restoreValue)
        {
            if (defaultValue == null) value = getPersistedString("00:00");
            else value = getPersistedString(defaultValue.toString());
        }
        else
        {
            value = defaultValue.toString();
        }

        hour = parseHour(value);
        minute = parseMinute(value);
    }

    public void persistStringValue(String value)
    {
        persistString(value);
    }
}

TimePreferenceDialogFragmentCompat.java

package com.test;

import android.content.Context;
import android.support.v7.preference.DialogPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceDialogFragmentCompat;
import android.view.View;
import android.widget.TimePicker;

public class TimePreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat implements DialogPreference.TargetFragment
{
    TimePicker timePicker = null;

    @Override
    protected View onCreateDialogView(Context context)
    {
        timePicker = new TimePicker(context);
        return (timePicker);
    }

    @Override
    protected void onBindDialogView(View v)
    {
        super.onBindDialogView(v);
        timePicker.setIs24HourView(true);
        TimePreference pref = (TimePreference) getPreference();
        timePicker.setCurrentHour(pref.hour);
        timePicker.setCurrentMinute(pref.minute);
    }

    @Override
    public void onDialogClosed(boolean positiveResult)
    {
        if (positiveResult)
        {
            TimePreference pref = (TimePreference) getPreference();
            pref.hour = timePicker.getCurrentHour();
            pref.minute = timePicker.getCurrentMinute();

            String value = TimePreference.timeToString(pref.hour, pref.minute);
            if (pref.callChangeListener(value)) pref.persistStringValue(value);
        }
    }

    @Override
    public Preference findPreference(CharSequence charSequence)
    {
        return getPreference();
    }
}

Required modifications in PreferenceFragmentCompat extension class

    public static class PreferencesFragment extends PreferenceFragmentCompat
    {
       ....

        @Override
        public void onDisplayPreferenceDialog(Preference preference)
        {
            DialogFragment dialogFragment = null;
            if (preference instanceof TimePreference)
            {
                dialogFragment = new TimePreferenceDialogFragmentCompat();
                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);
            }
        }
    }

With above code time preference can be used in preferences xml file like this

<com.test.TimePreference
    android:key="some_time"
    android:title="Set some time"
    android:defaultValue="12:00"
    android:summary="Set some time"/>

Solution 5 - Android

CommonsWare's solution has a few problems, which I fixed:

  • It doesn't update the field properly after it is changed
  • The minutes value only persists a single digit, e.g. 10:2 instead of 10:02
  • If you use PreferenceManager.setDefaultPreferences to set initial default preferences in your app, it won't work because onSetInitialValue needs to persist it
  • The formatting of the result isn't tailored to the user's Locale (e.g. US uses AM/PM)

Here's my code, enjoy.

public class TimePreference extends DialogPreference {
    private int lastHour=0;
    private int lastMinute=0;
    private TimePicker picker=null;

    public static int getHour(String time) {
        String[] pieces=time.split(":");

        return(Integer.parseInt(pieces[0]));
    }

    public static int getMinute(String time) {
        String[] pieces=time.split(":");

        return(Integer.parseInt(pieces[1]));
    }

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

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

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

        return(picker);
    }

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

        picker.setCurrentHour(lastHour);
        picker.setCurrentMinute(lastMinute);
    }

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

        if (positiveResult) {
            lastHour=picker.getCurrentHour();
            lastMinute=picker.getCurrentMinute();

            setSummary(getSummary());

            String lastMinuteString = String.valueOf(lastMinute);
            String time = String.valueOf(lastHour) + ":" + (lastMinuteString.length() == 1 ? "0" + lastMinuteString : lastMinuteString);

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

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

    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {

        String time;
        String defaultValueStr = (defaultValue != null) ? defaultValue.toString() : "00:00";
        if (restoreValue)
            time = getPersistedString(defaultValueStr);
        else {
            time = defaultValueStr;
            if (shouldPersist())
                persistString(defaultValueStr);
        }

        lastHour=getHour(time);
        lastMinute=getMinute(time);

        setSummary(getSummary());
    }

    @Override
    public CharSequence getSummary() {

        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.HOUR_OF_DAY, lastHour);
        cal.set(Calendar.MINUTE, lastMinute);
        DateFormat sdf = SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT);
        
        return sdf.format(cal.getTime());
    }

}

Solution 6 - Android

add this for Summary:

@Override
public CharSequence getSummary() {
	Calendar cal = Calendar.getInstance();
	cal.set(Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH, lastHour, lastMinute);
    return DateFormat.getTimeFormat(getContext()).format(new Date(cal.getTimeInMillis()));
}

and add

setSummary(getSummary());

to end of onSetInitialValue and onDialogClosed.

Solution 7 - Android

I have modified CommonsWare answer to use JodaTime library:

import android.content.Context;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TimePicker;

import org.joda.time.LocalTime;

public class TimePreference extends DialogPreference {
    private int lastHour;
    private int lastMinute;
    private TimePicker picker;

    public TimePreference(Context context, AttributeSet attrs) {
        super(context, attrs);

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

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

        return(picker);
    }

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

        picker.setCurrentHour(lastHour);
        picker.setCurrentMinute(lastMinute);
    }

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

        if (positiveResult) {
            lastHour = picker.getCurrentHour();
            lastMinute = picker.getCurrentMinute();

            LocalTime localTime = new LocalTime(lastHour, lastMinute);
            String time = localTime.toString();

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

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

    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
        LocalTime time;

        if (restoreValue) {
            if (defaultValue == null) {
                time = LocalTime.parse(getPersistedString("08:00:00.000"));
            }
            else {
                time = LocalTime.parse(getPersistedString(defaultValue.toString()));
            }
        } else {
            time = LocalTime.parse(defaultValue.toString());
        }
        
        lastHour = time.getHourOfDay();
        lastMinute = time.getMinuteOfHour();
    }
}

Also you will need to add a Custom preference like Sikora said.

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >

    <EditTextPreference
        android:key="editTextPref_Key"
        android:title="@string/editTextPref_title"/>
    <com.example.myapp.TimePreference
        android:key="timePrefA_Key"
        android:title="@string/timePrefA_title"/>    
    <com.example.myapp.TimePreference
        android:key="timePrefB_Key"
        android:title="@string/timePrefB_title"/>

</PreferenceScreen>

Solution 8 - Android

With Android 6, "current hour" and "current minute" are deprecated. Use this to ensure Marshmallow compatibility:

import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TimePicker;

public class TimePreference extends DialogPreference {

private int lastHour;
private int lastMinute;
private TimePicker picker;

public TimePreference(Context ctx, AttributeSet attrs) {
    super(ctx, attrs);
    setPositiveButtonText(ctx.getString(android.R.string.ok));
    setNegativeButtonText(ctx.getString(android.R.string.cancel));
}

@Override
protected View onCreateDialogView() {
    picker = new TimePicker(getContext());
    picker.setIs24HourView(true);
    return picker;
}

@SuppressWarnings("deprecation")
@Override
protected void onBindDialogView(View v) {
    super.onBindDialogView(v);
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        picker.setCurrentHour(lastHour);
        picker.setCurrentMinute(lastMinute);
    } else {
        picker.setHour(lastHour);
        picker.setMinute(lastMinute);
    }
}

@SuppressWarnings("deprecation")
@Override
protected void onDialogClosed(boolean positiveResult) {
    super.onDialogClosed(positiveResult);
    if (positiveResult) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            lastHour = picker.getCurrentHour();
            lastMinute = picker.getCurrentMinute();
        } else {
            lastHour = picker.getHour();
            lastMinute = picker.getMinute();
        }
        String time = String.valueOf(lastHour) + ":" + String.valueOf(lastMinute);
        if (callChangeListener(time)) {
            persistString(time);
        }
    }
}

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

@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
    String time;
    if (restoreValue) {
        if (defaultValue == null) {
            time = getPersistedString("00:00");
        } else {
            time = getPersistedString(defaultValue.toString());
        }
    } else {
        time = defaultValue.toString();
    }
    lastHour = getHour(time);
    lastMinute = getMinute(time);
}

public static int getHour(String time) {
    String[] pieces = time.split(":");
    return Integer.parseInt(pieces[0]);
}

public static int getMinute(String time) {
    String[] pieces = time.split(":");
    return Integer.parseInt(pieces[1]);
}
}

Solution 9 - Android

Like LEO87, I was seeing ClassCastException's. The problem was due to stale persisted data from a previous control of the same name. Possible solutions are to clear the app data, use a different name (key), or if you must use the same key name, catch the exception as follows:

@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
    if (restoreValue) {
        long persistedValue;
        try {
            persistedValue = getPersistedLong(System.currentTimeMillis());
        } catch (Exception e) {
            //Stale persisted data may be the wrong type
            persistedValue = System.currentTimeMillis();
        }
        calendar.setTimeInMillis(persistedValue);
    } else if (defaultValue != null) {
        calendar.setTimeInMillis(Long.parseLong((String) defaultValue));
    } else {
        //!restoreValue, defaultValue == null
        calendar.setTimeInMillis(System.currentTimeMillis());
    }

    setSummary(getSummary());
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionhszView Question on Stackoverflow
Solution 1 - AndroidCommonsWareView Answer on Stackoverflow
Solution 2 - AndroidDavid VávraView Answer on Stackoverflow
Solution 3 - AndroidSikoraView Answer on Stackoverflow
Solution 4 - AndroidDalija PrasnikarView Answer on Stackoverflow
Solution 5 - AndroidMatt KoalaView Answer on Stackoverflow
Solution 6 - AndroidOmid OmidiView Answer on Stackoverflow
Solution 7 - AndroidBinoy BabuView Answer on Stackoverflow
Solution 8 - Android0101100101View Answer on Stackoverflow
Solution 9 - AndroidpmontView Answer on Stackoverflow