DatePicker Data Binding

We’re very close to releasing the version 1.0 of data binding (we’re up to 1.0-rc4). There have been very few bug trickling in, so we know it is getting to be rock solid. I thought I’d talk about some of the features and how to implement some enhancements you might want to add to data binding for your application.

As you know, you can bind to almost all View attributes with an expression. For example, you can bind to a user’s name with the expression:

<TextView android:text="@{user.name}" .../>

You can even get callbacks for events you can set on the View. For example, if you want the user to be able to edit the name, you may use:

<EditText android:text="@{user.name}"
          android:afterTextChanged="@{handlers.nameChanged}" .../>

That said, DatePicker doesn’t have anything special associated with it. There’s no android:date or android:year property. You don’t even get them for free as automatic setters because there isn’t a setDate() or setYear() method, just an updateDate() method. Likewise, because there is no setter for the OnDateChanged listener, there is no android:onDateChanged support. It sure would be nice to have a date picker support this functionality.

Ideally, we could implement it in a single method:

@BindingAdapter({"android:year","android:month", "android:day",
                 "android:onDateChanged"})
public static void setDate(DatePicker view, int year, int month,
                           int day, OnDateChanged listener) {
    view.init(year, month, day, listener);
}

The above BindingAdapter will work when all of the attributes are supplied. If you want to have a BindingAdapter work when only some of the attributes are supplied, you should supply requireAll = false for the BindingAdapter annotation. When you do that, and the attribute doesn’t exist, the default value for type is supplied (0 for int, null for Objects, etc):

@BindingAdapter(value = {"android:year", "android:month",
                         "android:day", "android:onDateChanged"},
                required = false)
public static void setDate(DatePicker view, int year, int month,
                           int day, OnDateChanged listener) {
    if (year == 0) year = view.getYear();
    if (month == 0) month = view.getMonth();
    if (day == 0) day = view.getDayOfMonth();
    view.init(year, month, day, listener);
}

That doesn’t look hard! But wait a second, month = 0 is January! This won’t work at all. If the month expression is set January, the month will use the current value. We’re fortunate that year = 0 is not legal (in Gregorian Calendar, we traverse from 1BC to 1AD) and day = 0 is not legal as we count from the first day of the month.

At this point, I wish that I had implemented “these attributes are required, but not these” for data binding. But I didn’t, so we have to have several BindingAdapters to cover all cases:

  • year, month, day, onDateChanged
  • year, month, day
  • year, month, onDateChanged
  • year, month
  • month, day, onDateChanged
  • month, day
  • year, day, onDateChanged; requireAll = false

The final one is special. Since it doesn’t have a month, we can compare the values to the default values (0, null) and know whether to set them or use the default.

Take a look and see how DatePicker can be data bound with this BindingAdapter. Perhaps it will inspire you to add some cool BindingAdapters. I hope we’ll get lots of requests for BindingAdapters like these for the attributes that you use and would like to be built by default.

https://github.com/georgemount/DatePicker

Advertisements