Custom DialogPreference disappears after orientation change - android

First of all, sorry for my english.
I'm trying to make a custom 'day picker' dialog on my preference screen. I got the codes from Android Developer site and from Stackoverflow.
The dialog works 'well', it saves and loads the value (the layout contains a NumberPicker and a TextView). The problem is: the dialog disappears upon device rotation.
I have read several posts on this forum and other sites, but no luck. Can you help me ? I've spent hours so far to finding the issue, but I can't see how to solve it. Thanks.
The code:
package hu.test.android.demo.ui;
import hu.test.android.demo.AMdtApplication;
import hu.test.android.demo.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.NumberPicker;
public class DayPreference extends DialogPreference {
private NumberPicker picker = null;
private static int day;
public static int getDay () {
return day;
}
public DayPreference(Context ctxt, AttributeSet attrs) {
super(ctxt, attrs);
setDialogLayoutResource(R.layout.day_preference);
setPositiveButtonText (TestApplication.getContext().getResources().getString( R.string.save ));
setNegativeButtonText (TestApplication.getContext().getResources().getString( R.string.cancel ));
}
#Override
protected void onBindDialogView(View v) {
super.onBindDialogView (v);
picker = (NumberPicker) v.findViewById (R.id.day_preference_number_picker);
picker.setMinValue (1);
picker.setMaxValue (30);
picker.setValue (day);
}
#Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult) {
if (callChangeListener (day)) {
day = picker.getValue();
persistInt (day);
}
setSummary(getSummary());
}
}
#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) {
day = getPersistedInt (30);
}
else {
day = getPersistedInt ((Integer) defaultValue);
}
}
else {
day = (Integer) defaultValue;
}
setSummary (getSummary());
}
#Override
public CharSequence getSummary() {
return String.valueOf ( day ) + " " + TestApplication.getContext().getResources().getString( R.string.label_day );
}
// state save & restore
#Override
protected Parcelable onSaveInstanceState () {
final Parcelable superState = super.onSaveInstanceState();
if (isPersistent()) {
return superState;
}
final SavedState myState = new SavedState (superState);
myState.value = day;
return myState;
}
#Override
protected void onRestoreInstanceState (Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {
super.onRestoreInstanceState(state);
return;
}
SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());
// set the value to the picker
picker.setValue (myState.value);
}
private static class SavedState extends BaseSavedState {
public int value;
public SavedState (Parcelable superState) {
super(superState);
}
public SavedState (Parcel source) {
super(source);
value = source.readInt();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(value);
}
// Standard creator object using an instance of this class
#SuppressWarnings( "unused" )
public static final Parcelable.Creator <SavedState> CREATOR = new Parcelable.Creator <SavedState> () {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray (int size) {
return new SavedState[size];
}
};
}
}

UPDATE:
Please note that the below solution is just a quick hack(and is not a recommended approach), proper solution would be to persist values during onSaveInstanceState and then getting those values after your activity is recreated.
This post is a must read to gracefully handle configuration changes.
ORIGINAL ANSWER:
Maybe this will help, add this line inside the relevant activity tag in your manifest file:
android:configChanges="orientation|screenSize"
This will ensure that onCreate is not called when the screen rotates, you say to the android OS, hey i will be handling the rotation stuff myself for this activity you don't need to start over from onCreate.

Related

Custom preference isPersistent()

I wrote my own number picker preference using android's brief guide and some googling. My question is regarding the onSaveInstanceState() method. On google's tutorial, it is suggested that we use the method isPersistent() to determine if the preference is persistent and if it is, then just return the superstate. I didn't do that because with this condition, if I swipe the number picker to a new number and then rotate the screen, the rotated version will return back to the persisted value. If I remove this condition then everything is ok. However, checking the source code of other preferences, like edittextpreference, this condition exists and the state is saved even if I change the value to an unsaved one and then rotate the screen.. Can somebody explain that please?
Here is my code:
public class NumberPreference extends DialogPreference {
private final static int DEFAULT_VALUE=R.integer.timer_def;
private final static int DEFAULT_MIN_VALUE=R.integer.timer_min_def;
private final static int DEFAULT_MAX_VALUE=R.integer.timer_max_def;
private final int min;
private final int max;
private final String time;
private int timer;
private NumberPicker numberPicker;
public NumberPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setDialogLayoutResource(R.layout.number_preference);
setNegativeButtonText("Cancel");
setPositiveButtonText("OK");
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.number_preference, 0, 0);
try{
min=a.getInteger(R.styleable.number_preference_min, DEFAULT_MIN_VALUE);
max=a.getInteger(R.styleable.number_preference_max, DEFAULT_MAX_VALUE);
time=a.getString(R.styleable.number_preference_time);
}finally{
a.recycle();
}
setDialogIcon(null);
}
public void setSummary() {
super.setSummary("Every "+getTimer()+' '+time);
}
#Override
protected View onCreateView(ViewGroup parent) {
setSummary();
return super.onCreateView(parent);
}
#Override
protected void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
int number = numberPicker.getValue();
if (callChangeListener(number)){
timer=number;
persistInt(timer);
setSummary();
}
}
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getInt(index,DEFAULT_VALUE);
}
#Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
if (restorePersistedValue) {
timer = getPersistedInt(DEFAULT_VALUE);
}
else{
timer =(Integer) defaultValue;
persistInt(timer);
}
}
#Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
numberPicker=(NumberPicker) view.findViewById(R.id.numpref_picker);
numberPicker.setMinValue(min);
numberPicker.setMaxValue(max);
numberPicker.setValue(timer);
}
public int getTimer() {
return getPersistedInt(DEFAULT_VALUE);
}
#Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
if (isPersistent()) {
return superState;
}
final SavedState myState=new SavedState(superState);
if (numberPicker!= null) myState.value=numberPicker.getValue();
return myState;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (state==null || !state.getClass().equals(SavedState.class)){
super.onRestoreInstanceState(state);
return;
}
SavedState myState=(SavedState)state;
super.onRestoreInstanceState(myState.getSuperState());
if (numberPicker!=null)numberPicker.setValue(myState.value);
}
private static class SavedState extends BaseSavedState {
// field that holds the setting's value
int value;
public SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
// Get the current preference's value
value = source.readInt();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
// Write the preference's value
dest.writeInt(value);
}
// Standard creator object using an instance of this class
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}
Thanks:)
without having tested it, would assume you might have to change
private final String time;
to
private String time;
and pass String.valueOf(time) as an argument into the .setSummary() method. of course, the occurrence in .onCreateView() would need to pass the value read from the preferences - or the default value as fallback, if nothing had been returned. try to make in more simple, than more complex.

Can PreferenceActivity customize how it handles touches?

I've been recently introduced to PreferenceActivity and would like to change how I handle interaction with an EditTextPreference defined in the xml.
I've put logs, toast and breakpoints over where boilerplate has overridden onListItemClick( but nothing is getting back to me. I've even tried stepping into the super class and was able to set breakpoints on the if and return successfully although they weren't ultimately trapping.
protected void onListItemClick(ListView l, View v, int position, long id) {
if (!isResumed()) {
return;
}
super.onListItemClick(l, v, position, id);
Thanks for looking
EDIT #DanielLe, here is my code:
//This isn't getting called?!
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
String selection = l.getItemAtPosition(position).toString();
Toast.makeText(this, selection, Toast.LENGTH_LONG).show();
Log.d("Activity", "onListItemClick=" + l.getItemAtPosition(position).toString());
super.onListItemClick(l, v, position, id);
}
At the risk of repeating what's gone before, one solution is to extend DialogPreferences as described in the Google guide for Android dev. It's only showing ok and cancel buttons which I believe makes it the minimum for a persisting DialogPreferences implementation:
Android: Creating custom preference
import android.content.Context;
import android.content.DialogInterface;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.DialogPreference;
import android.preference.EditTextPreference;
import android.util.AttributeSet;
import android.widget.Button;
import android.widget.EditText;
public class ClickablePreference extends DialogPreference {
private String mNewValue;
private EditText mEditText;
public ClickablePreference(Context context, AttributeSet attrs) {
super(context, attrs);
setDialogLayoutResource(R.layout.dir_picker_dialog);
setPositiveButtonText(android.R.string.ok);
setNegativeButtonText(android.R.string.cancel);
setDialogIcon(null);
}
#Override
protected void onDialogClosed(boolean positiveResult) {
// When the user selects "OK", persist the new value
if (positiveResult) {
persistString(mNewValue);
}
}
private static class SavedState extends BaseSavedState {
// Member that holds the setting's value
// Change this data type to match the type saved by your Preference
String value;
public SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
// Get the current preference's value
value = source.readString(); // Change this to read the appropriate data type
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
// Write the preference's value
dest.writeString(value); // Change this to write the appropriate data type
}
// Standard creator object using an instance of this class
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
#Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
if (restorePersistedValue) {
// Restore existing state
mNewValue = this.getPersistedString("");
} else {
// Set default state from the XML attribute
mNewValue = (String) defaultValue;
persistString(mNewValue);
}
}
#Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
// Check whether this Preference is persistent (continually saved)
if (isPersistent()) {
// No need to save instance state since it's persistent,
// use superclass state
return superState;
}
// Create instance of custom BaseSavedState
final SavedState myState = new SavedState(superState);
// Set the state's value with the class member that holds current
// setting value
myState.value = mNewValue;
return myState;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
// Check whether we saved the state in onSaveInstanceState
if (state == null || !state.getClass().equals(SavedState.class)) {
// Didn't save the state, so call superclass
super.onRestoreInstanceState(state);
return;
}
// Cast state to custom BaseSavedState and pass to superclass
SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());
// Set this Preference's widget to reflect the restored state
mEditText.setText(myState.value);
}
#Override
protected void onClick() {
super.onClick();
}
#Override
public void onClick(DialogInterface dialog, int which) {
super.onClick(dialog, which);
}
}
I've yet to look at Concise way of writing new DialogPreference classes? in detail although it seems very similar to what I got from Google.
The problem with this solution, apart from its size, is that mEditText is unused and I couldn't actually grab a reference to the displayed EditText defined in xml.
Roll on Solution 2
With thanks to Android: launch a custom Preference from a PreferenceActivity
just tagged this on the end of onPostCreate from MyPreferenceActivity
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
setupSimplePreferencesScreen();
Preference customPref = (Preference) findPreference("pref_do_something");
customPref.setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
Log.d("Activity", "onPreferenceClick=" + preference.toString());
return true;
}
});
}
Far better :)

Android DialogPreference NullPointerException in onRestoreInstanceState

I am trying to implement a DialogPreference with two NumberPicker objects, that restores the last changed NumberPicker values after orientation change:
public class CustomTimePreference extends DialogPreference {
public NumberPicker firstPicker, secondPicker;
private int lastHour = 0;
private int lastMinute = 15;
private int firstMaxValue;
private int tempHour;
private int tempMinute;
private int rotatedHour;
private int rotatedMinute;
private int firstMinValue = 0;
private int secondMinValue=0;
private int secondMaxValue=59;
private String headerText;
private boolean usedForApprox;
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 CustomTimePreference(Context context){
this(context, null);
}
public CustomTimePreference(Context context, AttributeSet attrs){
super(context, attrs);
init(attrs);
setDialogLayoutResource(R.layout.custom_time_preference);
setPositiveButtonText(context.getString(R.string.time_preference_set_text));
setNegativeButtonText(context.getString(R.string.time_preference_cancel_text));
}
private void init(AttributeSet attrs){
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CustomTimePreference);
firstMaxValue = a.getInteger(R.styleable.CustomTimePreference_firstMaxValue,10);
usedForApprox = a.getBoolean(R.styleable.CustomTimePreference_usedForApproximate, false);
headerText = a.getString(R.styleable.CustomTimePreference_customTimeDialogTopText);
a.recycle();
}
public void setFirstPickerValue(int value){
firstPicker.setValue(value);
}
public void setSecondPickerValue(int value){
secondPicker.setValue(value);
}
#Override
protected View onCreateDialogView(){
Log.d("OnCreateDialogView","nanana");
View root = super.onCreateDialogView();
TextView tv = (TextView)root.findViewById(R.id.custom_time_preference_title);
tv.setText(headerText);
firstPicker = (NumberPicker)root.findViewById(R.id.time_preference_first_picker);
firstPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
#Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// TODO Auto-generated method stub
tempHour = newVal;
}
});
secondPicker = (NumberPicker)root.findViewById(R.id.time_preference_second_picker);
secondPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
#Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// TODO Auto-generated method stub
tempMinute = newVal;
}
});
if(usedForApprox){
int smallestValue = MainActivity.getShortestPeriodLength(getContext());
int second = smallestValue % 60;
second-=1;
firstPicker.setMaxValue(second);
secondPicker.setMaxValue(59);
} else {
firstPicker.setMaxValue(firstMaxValue);
secondPicker.setMaxValue(secondMaxValue);
}
firstPicker.setMinValue(firstMinValue);
secondPicker.setMinValue(secondMinValue);
return root;
}
#Override
protected void onBindDialogView(View v){
super.onBindDialogView(v);
firstPicker.setValue(lastHour);
secondPicker.setValue(lastMinute);
}
#Override
protected void onDialogClosed(boolean positiveResult){
super.onDialogClosed(positiveResult);
if(positiveResult){
lastHour = firstPicker.getValue();
lastMinute = secondPicker.getValue();
if (lastHour ==0 && lastMinute == 0){
lastMinute =1;
}
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 = tempHour = getHour(time);
lastMinute = tempMinute = getMinute(time);
}
private static class SavedState extends BaseSavedState {
// Member that holds the setting's value
// Change this data type to match the type saved by your Preference
String value;
public SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
// Get the current preference's value
value = source.readString(); // Change this to read the appropriate data type
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
// Write the preference's value
dest.writeString(value); // Change this to write the appropriate data type
}
// Standard creator object using an instance of this class
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
#Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
// Check whether this Preference is persistent (continually saved)
/*
if (isPersistent()) {
// No need to save instance state since it's persistent, use superclass state
return superState;
}
*/
// Create instance of custom BaseSavedState
final SavedState myState = new SavedState(superState);
// Set the state's value with the class member that holds current setting value
myState.value = String.valueOf(tempHour) + ":" + String.valueOf(tempMinute);
return myState;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
// Check whether we saved the state in onSaveInstanceState
if (state == null || !state.getClass().equals(SavedState.class)) {
// Didn't save the state, so call superclass
super.onRestoreInstanceState(state);
return;
}
// Cast state to custom BaseSavedState and pass to superclass
SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());
// Set this Preference's widget to reflect the restored state
rotatedHour = getHour(myState.value);
rotatedMinute = getMinute(myState.value);
firstPicker.setValue(rotatedHour);
secondPicker.setValue(rotatedMinute);
}
}
There are two problems:
The app crashes, when i open my custom preference, change values on one of the pickers, and then rotate the phone from portrait to landscape. The error is NuLLpointerException and it points to the line where i try to assing the restored value to one of my NumberPicker objects.
This is more a question than a problem. I copied the BaseSavedState inner class and both onSaveInstanceState(), onRestoreInstanceState() functions from Android Developer homepage, but when i tried the app to restore values on orientation change, the phone showed the persisted values, not the latest values before orientation change. When i tried to examine the code with log messages, i discovered that my phone on SaveInstanceState isPersisted check exits the function and doesn`t operate with BaseSavedState object att all. I comented out that isPersisted check so now i can save and retrieve values from BaseSavedState object, but after that the problem nr. 1 appears. So my question is, what is the reasoning to skip the BaseSavedState object creation, if the preference is persistent. And is my decision to skip the persistent check and force the app to create BaseSavedState object a bad one?
Take a look at this implementation with 3 NumberPicker objects:
package com.bom.dom;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.NumberPicker;
import android.widget.NumberPicker.OnValueChangeListener;
public class TimePreference extends DialogPreference {
NumberPicker hoursNumberPicker;
NumberPicker minutesNumberPicker;
NumberPicker secondsNumberPicker;
int time;
int currentTime;
public TimePreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onBindDialogView(View view) {
hoursNumberPicker = (NumberPicker) view.findViewById(R.id.numberpicker_hours);
hoursNumberPicker.setMaxValue(24);
hoursNumberPicker.setMinValue(0);
hoursNumberPicker.setOnValueChangedListener(new OnValueChangeListener() {
#Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
updateCurrentTimeFromUI();
}
});
minutesNumberPicker = (NumberPicker) view.findViewById(R.id.numberpicker_minutes);
minutesNumberPicker.setMaxValue(59);
minutesNumberPicker.setMinValue(0);
minutesNumberPicker.setOnValueChangedListener(new OnValueChangeListener() {
#Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
updateCurrentTimeFromUI();
}
});
secondsNumberPicker = (NumberPicker) view.findViewById(R.id.numberpicker_seconds);
secondsNumberPicker.setMaxValue(59);
secondsNumberPicker.setMinValue(0);
secondsNumberPicker.setOnValueChangedListener(new OnValueChangeListener() {
#Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
updateCurrentTimeFromUI();
}
});
updateUI();
super.onBindDialogView(view);
}
#Override
protected void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
time = currentTime;
persistInt(time);
return;
}
currentTime = time;
}
private void updateCurrentTimeFromUI() {
int hours = hoursNumberPicker.getValue();
int minutes = minutesNumberPicker.getValue();
int seconds = secondsNumberPicker.getValue();
currentTime = hours * 3600 + minutes * 60 + seconds;
}
#Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
if (restorePersistedValue) {
time = getPersistedInt(1);
} else {
time = (Integer) defaultValue;
persistInt(time);
}
currentTime = time;
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
Integer defaultValue = a.getInteger(index, 1);
return defaultValue;
}
private void updateUI() {
int hours = (int) (currentTime / 3600);
int minutes = ((int) (currentTime / 60)) % 60;
int seconds = currentTime % 60;
hoursNumberPicker.setValue(hours);
minutesNumberPicker.setValue(minutes);
secondsNumberPicker.setValue(seconds);
}
#Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
final SavedState myState = new SavedState(superState);
myState.value = currentTime;
return myState;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {
super.onRestoreInstanceState(state);
return;
}
SavedState myState = (SavedState) state;
currentTime = myState.value;
super.onRestoreInstanceState(myState.getSuperState());
}
private static class SavedState extends BaseSavedState {
int value;
public SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
value = source.readInt();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(value);
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}
The layout file is:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<NumberPicker
android:id="#+id/numberpicker_hours"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<NumberPicker
android:id="#+id/numberpicker_minutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<NumberPicker
android:id="#+id/numberpicker_seconds"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
I found the solution on first problem:
To avoid the NullPointerException, i had to enclose the functions, that access NumberPicker objects, with a check, that determines if those pickers are initiated. I had this problem because on my app i have multiple my custom preference instances, and when i tried to follow the data path for saving/restoring functions, i discovered, that in logcat i had twice the amount of my own messages (not only for my onscreen preference, but also for the other prefrerence that uses my custom DialogPreference class). And because, i opened only one preference, the initialisation of NumberPicker objects in other preference didn`t happened, so accessing those pickers led (if i understood correctly) to NullPointerException.
But i still would like to hear someone more experienced, that could explain the default behaviour with save/restore functions.

TimePicker in PreferenceScreen

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 ?
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);
}
}
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()));
}
}
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)
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"/>
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());
}
}
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.
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>
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]);
}
}
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());
}

How to use DatePickerDialog as a Preference

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.
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" />
You would need to create a custom DialogPreference incorporating a DatePicker.
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.
I will show my code to select and save the date from SettingsActivity.java, applied in the Preferences fragment class. Works on every devices, from API level 16, to API level 29.
First, put this tag between <PreferenceScreen>, or between <PreferenceCategory> tag (if you have) in your "root_preferences.xml" file:
<Preference
app:key="date_pref"
app:title="Set date"
app:useSimpleSummaryProvider="true" />
And final, put this code in the SettingsActivity.java file, in the sub class named SettingsFragment:
public static class SettingsFragment extends PreferenceFragmentCompat {
SharedPreferences settings;
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
settings = requireContext().getSharedPreferences(requireContext().getPackageName() + "_preferences", Context.MODE_PRIVATE);
setPreferencesFromResource(R.xml.root_preferences, rootKey);
Preference date_pref = getPreferenceManager().findPreference("date_pref");
assert date_pref != null;
date_pref.setSummary(settings.getString("date_pref", ""));
date_pref.setOnPreferenceClickListener(datePicker);
}
Preference.OnPreferenceClickListener datePicker = new Preference.OnPreferenceClickListener() {
#Override
public boolean onPreferenceClick(Preference preference) {
Calendar cal = Calendar.getInstance();
int cal_month = cal.get(Calendar.MONTH);
int cal_day = cal.get(Calendar.DAY_OF_MONTH);
int cal_year = cal.get(Calendar.YEAR);
DatePickerDialog datePickerDialog = new DatePickerDialog(requireActivity(), new DatePickerDialog.OnDateSetListener() {
#Override
public void onDateSet(DatePicker view, int year, int month, int day) {
String datePicked = day + "." + month + "." + year;
settings.edit().putString("date_pref", datePicked).apply();
preference.setSummary(datePicked);
}
}, cal_year, cal_month, cal_day);
datePickerDialog.show();
return false;
}
};
}
Don't forget this imports:
import android.app.DatePickerDialog;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.Calendar;
And don't forget to Vote Up my post :)

Categories

Resources