Search code examples
androidandroid-layoutdata-bindingandroid-view2-way-object-databinding

Two-Way Data Binding: View is missing user defined type


Today I found out about the very-recently-introduced two-way data binding capability in the Android Studio preview, and decided to give it a try.

I have a very simple layout (code below), for composing and sending messages. What I was trying to achieve is having the button "disabled" (and in the future, have some different image accordingly) when there is no text entered in the field.

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android">
<data>
    <variable name="msg" type="String"/>
</data>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <EditText
        android:id="@+id/new_message_input"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:paddingStart="10dp"
        android:hint="@string/hint_compose_message"
        android:inputType="textAutoCorrect|textMultiLine"
        android:text="@={msg}"/>
    <ImageButton
        android:id="@+id/btn_send_message"
        android:layout_width="50dp"
        android:layout_height="match_parent"
        android:src="@drawable/ic_send"
        android:enabled="@{!new_message_input.text.isEmpty()}"
        android:clickable="@{!new_message_input.text.isEmpty()}"/>
</LinearLayout>
</layout>

The example code in the first link shows that something like this should be enough:

<layout ...>
  <data>
    <import type="android.view.View"/>
  </data>
  <RelativeLayout ...>
    <CheckBox android:id="@+id/seeAds" .../>
    <ImageView android:visibility="@{seeAds.checked ? View.VISIBLE : View.GONE}" .../>
  </RelativeLayout>
</layout>

However, when trying to implement similar logic for the enabled/clickable properties of the ImageButton, I am getting the following error:

Error:java.lang.RuntimeException: java.lang.RuntimeException: Found data binding errors. ****/ data binding error ****msg:Identifiers must have user defined types from the XML file. new_message_input is missing it

The problem is definitely with these two lines, since removing them allows the binding class to be properly created.

My questions are:

  • What am I doing wrong?
  • How can I resolve this?

I also tried doing it a bit differently, but the result was the same:

<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
    <import type="android.widget.EditText"/>
    ...
</data>
<LinearLayout
  ...
  <ImageButton
    ...
    android:enabled="@{!(((EditText)new_message_input).getText().toString().isEmpty())}"
    android:clickable="@{!(((EditText)new_message_input).getText().toString().isEmpty())}"/>

Solution

  • Doh.

    It slipped my mind that the the Data Binding process converts XML IDs to properties in the Binding class, all of which are written in lower camel case. This means that in order to refer to "@id/new_message_input" from within "@id/btn_send_message" using data binding, I should have used the generated name, which in this case was newMessageInput.

    This was not immediately evident from the example because it contained a View whose @id was already in camelCase and therefore identical to the the generated name - thus working automagically.

    The solution was therefore to replace these lines:

        android:enabled="@{!new_message_input.text.isEmpty()}"
        android:clickable="@{!new_message_input.text.isEmpty()}"/>
    

    with:

        android:enabled="@{!newMessageInput.text.isEmpty()}"
        android:clickable="@{!newMessageInput.text.isEmpty()}"/>
    

    Or I could completely get around this problem if I did:

        android:enabled="@{!msg.isEmpty()}"
        android:clickable="@{!msg.isEmpty()}"/>
    

    On a side note:

    If the EditText is initially empty (and therefore we would expect the button to be disabled), we should attach an empty String object to the view (through Java; e.g. StringUtils.EMPTY), at the time of layout inflation, to correctly make the button unclickable.