I want to use two-way databinding with Android LiveData
components (as an alternative for Observable fields. Here's code for simple project with CalendarView
and EditText
that displays both info on button clicked.
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="testDate"
type="android.arch.lifecycle.MutableLiveData<Long>" />
<variable
name="testString"
type="android.arch.lifecycle.MutableLiveData<String>" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onButtonClick"
android:text="Show data"/>
<CalendarView
android:id="@+id/cal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:date="@={testDate}"/>
<EditText
android:id="@+id/str"
android:text="@={testString}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>
And activity code:
class MainActivity : AppCompatActivity() {
val liveDate = MutableLiveData<Long>().apply { value = System.currentTimeMillis() }
val liveString = MutableLiveData<String>().apply { value = "Date: " }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
.also {
it.testDate = liveDate
it.testString = liveString
it.cal.setOnDateChangeListener { view, year, month, dayOfMonth ->
Toast.makeText(applicationContext, "${view.date}", Toast.LENGTH_SHORT).show()
}
}
}
@TargetApi(Build.VERSION_CODES.O)
fun onButtonClick(view: View) {
Toast.makeText(this, liveString.value +
Instant.ofEpochMilli(liveDate.value!!).atZone(ZoneId.systemDefault()).toLocalDate()
, Toast.LENGTH_SHORT)
.show()
}
}
Two-way binding works fine for String, but not for the date. I found this post, which says that selected date is actually something different from the date used by android:date
... Fair enough, you can capture this action of changing the date in the listener. The problem is that listener setOnDateChangeListener
(applied in also {}
above) is not triggered at all, when there is a two-way binding setup.
Correct me if I'm wrong, but if I want to get the selected date I have to use OnDateChangeListener
. It also appears to be incompatible with android:date@={...}
because using two-way binding seems to override our listener. It would make sense if android:date@={...}
provided the same functionality as OnDateChangeListener
, but it does not.
So the final question is: is it possible to get the selected date somehow with two-way databinding?
There are bunch of bugs, which I found after tracking all classes.
Bug 1
This is bug of Android Documentation. See CalendarViewBindingAdapter class.
You can see they have created binding adapter for android:date
, but there is no @InverseBindingAdapter
.
@BindingAdapter({"android:date"})
public static void setDate(CalendarView view, long date) {
if (view.getDate() != date) {
view.setDate(date);
}
}
// no @InverseBindingAdapter written
But on documentation, they have written that CalendarView
supports two-way binding.
Perhaps we will get this in next updates.
I also tried to add @InverseBindingAdapter
but that was not working too.
@InverseBindingAdapter(attribute = "android:date", event = "android:dateAttrChanged")
public static long getDateLong(CalendarView view) {
return view.getDate();
}
Bug 2
Try setting setOnDateChangeListener
on CalendarView
, you will get same date always.
Below does not work
binding.cal.setOnDateChangeListener((view, year, month, dayOfMonth) -> {
Log.d(TAG, "aLong: " + new Date(view.getDate()).toString());
});
Below works
binding.cal.setOnDateChangeListener((view, year, month, dayOfMonth) -> {
Log.d(TAG, "aLong: " + new Date(year, month, dayOfMonth).toString());
});
That's why my @InverseBindingAdapter
does not work.
Because calendarView.getDate()
is not giving correct date.
Fix
You can fix this by creating your adapter till they don't fix this issue. Just put below class in your project, and everything will work well.
public class CalendarViewBindingAdapter {
@BindingAdapter(value = {"android:onSelectedDayChange", "android:dateAttrChanged"},
requireAll = false)
public static void setListeners(CalendarView view, final CalendarView.OnDateChangeListener onDayChange,
final InverseBindingListener attrChange) {
if (attrChange == null) {
view.setOnDateChangeListener(onDayChange);
} else {
view.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
@Override
public void onSelectedDayChange(CalendarView view, int year, int month,
int dayOfMonth) {
if (onDayChange != null) {
onDayChange.onSelectedDayChange(view, year, month, dayOfMonth);
}
Calendar instance = Calendar.getInstance();
instance.set(year, month, dayOfMonth);
view.setDate(instance.getTimeInMillis());
attrChange.onChange();
}
});
}
}
}
What I fixed
I just set date to CalendarView (view.setDate()
), which was 0 previously.
来源:https://stackoverflow.com/questions/52389235/using-calendarview-with-databinding