Reputation: 1765
I'm trying to create an app where the user selects a date from a DatePicker, and then a list is updated with some values.
My GUI looks like this:
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" >
<DatePicker
android:id="@+id/datePicker"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1" >
</ListView>
</LinearLayout>
Whereas my DatePicker initialization and handling look as follows:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
datePicker = (DatePicker) findViewById(R.id.datePicker);
Calendar c = Calendar.getInstance();
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH);
day = c.get(Calendar.DAY_OF_MONTH);
datePicker.init(year, month, day, dateSetListener);
}
private DatePicker.OnDateChangedListener dateSetListener = new DatePicker.OnDateChangedListener() {
public void onDateChanged(DatePicker view, int year, int monthOfYear,
int dayOfMonth) {
Calendar c = Calendar.getInstance();
c.set(year, monthOfYear, dayOfMonth);
System.out.println ("TEST");
}
};
In CatLog I see that "TEST" string is output twice, each time I play with the +/- buttons on the widget. What could be the problem?
Note: I've "disabled" the list-updating code on purpose, in order to make sure that the problem is not related to the ListView, as in here
Upvotes: 48
Views: 23616
Reputation:
When I test my application, method onDateSet called twice after accept the date selection and once when I canceled.
I added a validation in the method onDateSet with parameter view, something like this
@Override
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth){
if (view.isShown()) {
updateDate(year, monthOfYear, dayOfMonth);
}
}
Upvotes: 143
Reputation: 697
I created a SupportDatePickerDialog.kt class
import android.app.DatePickerDialog
import android.content.Context
import android.os.Build
import android.widget.DatePicker
import androidx.appcompat.view.ContextThemeWrapper
/**
* Support [DatePickerDialog]
* for working around Samsung 5 [java.util.IllegalFormatConversionException] bug.
* > Fatal Exception: java.util.IllegalFormatConversionException: %d can't format java.lang.String arguments
* http://stackoverflow.com/a/31855744/570168
* for OnDateSetListener called twice bug
* https://stackoverflow.com/questions/12436073/datepicker-ondatechangedlistener-called-twice
*
*/
class SupportDateSetListener(private val function: (DatePicker, Int, Int, Int) -> Unit) : DatePickerDialog.OnDateSetListener {
override fun onDateSet(view: DatePicker, year: Int, month: Int, dayOfMonth: Int) {
if (view.isShown) {
function.invoke(view, year, month, dayOfMonth)
}
}
}
class SupportDatePickerDialog(context: Context, listener: SupportDateSetListener, year: Int, month: Int, dayOfMonth: Int) : DatePickerDialog(fixContext(context), listener, year, month, dayOfMonth) {
companion object {
/**
* Wraps the [Context] to use the holo theme to avoid stupid bug on Samsung devices.
*/
@Suppress("DEPRECATION")
private fun fixContext(context: Context): Context {
return if (isBrokenSamsungDevice) {
ContextThemeWrapper(context, android.R.style.Theme_Holo_Light_Dialog)
} else {
context
}
}
/**
* Affected devices:
* - Samsung 5.0
* - Samsung 5.1
*
* @return true if device is affected by this bug.
*/
@Suppress("SpellCheckingInspection")
private val isBrokenSamsungDevice: Boolean
get() = (Build.MANUFACTURER.equals("samsung", ignoreCase = true)
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1)
}
}
Usage :
val calendar = Calendar.getInstance()
val supportDatePickerDialog = SupportDatePickerDialog(this, SupportDateSetListener { datePicker: DatePicker, year: Int, month: Int, dayOfMonth: Int ->
//TODO
}, calendar[Calendar.YEAR], calendar[Calendar.MONTH], calendar[Calendar.DAY_OF_MONTH])
supportDatePickerDialog.show()
Upvotes: 0
Reputation: 4635
Instead of the onDateSet callback I've overridden onClick method of the DatePickerDialog dialog and handle BUTTON_POSITIVE clicks only.
public static class DatePickerFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Calendar c = Calendar.getInstance();
int initialYear = c.get(Calendar.YEAR);
int initialMonth = c.get(Calendar.MONTH);
int initialDay = c.get(Calendar.DAY_OF_MONTH);
return new DatePickerDialog(getActivity(), null, initialYear, initialMonth, initialDay) {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == BUTTON_POSITIVE) {
int year = getDatePicker().getYear();
int month = getDatePicker().getMonth();
int day = getDatePicker().getDayOfMonth();
//TODO: Do your logic here
}
super.onClick(dialog, which);
}
};
}
}
The code stays clear but still it's not clear why onDateSet is called twice.
Upvotes: 2
Reputation: 29
Not necessary to add this code under Jelly beans
@Override
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth){
if (view.isShown()) {
updateDate(year, monthOfYear, dayOfMonth);
}
}
Upvotes: 0
Reputation: 11
private DatePicker.OnDateChangedListener dateSetListener = new DatePicker.OnDateChangedListener() {
boolean fired = false;
public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
if (fired == true){
//Twince
}
else{
Calendar c = Calendar.getInstance();
c.set(year, monthOfYear, dayOfMonth);
System.out.println ("TEST");
}
}
};
Upvotes: 0
Reputation: 783
Perhaps modify the listener by adding instance variables, that way you can check to see if they are different from the last time the method was called:
final DatePickerDialog datePickerDialog = new DatePickerDialog (this, new DatePickerDialog.OnDateSetListener()
{
private int year;
private int month;
private int day;
@Override
public void onDateSet (DatePicker view, int year, int monthOfYear, int dayOfMonth)
{
if (this.year == year && this.month == monthOfYear && this.day == dayOfMonth)
return;
this.year = year;
this.month = monthOfYear;
this.day = dayOfMonth;
calendar.set (year, month, day);
int index = officeCalendar.indexOfKey (calendar.getTimeInMillis ());
if (index > -1)
viewPager.setCurrentItem (index);
}
}, calendar.get (Calendar.YEAR), calendar.get (Calendar.MONTH), calendar.get (Calendar.DATE));
Upvotes: 0
Reputation: 1765
I still haven't managed to find a neat fix. I've rather found a workaround, so the event does not fire twice. The workaround is as follows:
int timesCalled = 1;
private DatePicker.OnDateChangedListener dateSetListener = new DatePicker.OnDateChangedListener() {
public void onDateChanged(DatePicker view, int year, int monthOfYear,
int dayOfMonth) {
Calendar c = Calendar.getInstance();
c.set(year, monthOfYear, dayOfMonth);
timesCalled += 1;
if ((timesCalled % 2) == 0) {
System.out.println ("TEST");
}
}};
Upvotes: 9
Reputation: 72
private DatePicker.OnDateChangedListener dateSetListener = new DatePicker.OnDateChangedListener() {
public void onDateChanged(DatePicker view, int year, int monthOfYear,
int dayOfMonth) {
Calendar c = Calendar.getInstance();
c.set(year, monthOfYear, dayOfMonth);
updateDisplay();
}
};
TextView datetext;
datetext = (TextView) findViewById(R.id.selected_date);
private void updateDisplay() {
this.datetext.setText(new StringBuilder()
// Month is 0 based so add 1
.append(mMonth + 1).append("-").append(mDay).append("-")
.append(mYear).append(" "));
}
Upvotes: 0