2-way Data Binding on Android!

Released with Android Studio 2.1 Preview 3, Android Data Binding now has 2-way data binding.

Data Binding Quick Recap

For a short recap on data binding, you can now add expressions to the layout files to reference variables in your data model. For example:

<layout ...>
  <data>
    <variable type="com.example.myapp.User" name="user"/>
  </data>
  <RelativeLayout ...>
    <TextView android:text="@{user.firstName}" .../>
  </RelativeLayout>
</layout>

The above binds the user’s first name to the TextView’s text field. The UI is updated whenever the user’s data has changed.

Two-Way Data Binding

Android isn’t immune to typical data entry and it is often important to reflect changes from the user’s input back into the model. For example, if the above data were in a contact form, it would be nice to have the edited text pushed back into the model without having to pull the data from the EditText. Here’s how you do it:

<layout ...>
  <data>
    <variable type="com.example.myapp.User" name="user"/>
  </data>
  <RelativeLayout ...>
    <EditText android:text="@={user.firstName}" .../>
  </RelativeLayout>
</layout>

Pretty nifty, eh? The only difference here is that the expression is marked with “@={}” instead of “@{}”. It is expected that most data binding will continue to be one-way and we don’t want to have all those listeners created and watching for changes that will never happen.

Implicit Attribute Listeners

You can also reference attributes on other Views:

<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>

In the above, whenever the checked state of CheckBox changes, the ImageView’s visibility will change. No need to attach a listener on your own! This kind of expression only works with attributes that support 2-way data binding and those that have binding expressions.

Enabling Two-Way Data Binding

So, what else do you need? First, you must be sure to be using the 2.1-alpha3 or above version of the android gradle plugin in your project’s build.gradle:

classpath 'com.android.tools.build:gradle:2.1.0-alpha3'

And, of course, you need to enable data binding in your module’s android section of the build.gradle:

android {
    ...
    dataBinding.enabled = true
}

Catch?

So, what’s the catch? Well, the main one is that only a few attributes are supported. This is because there aren’t listeners to allow the data binding framework to know when something has changed. The good news is that these are probably the attributes you care most about:

  • AbsListView android:selectedItemPosition
  • CalendarView android:date
  • CompoundButton android:checked
  • DatePicker android:year, android:month, android:day (yes, these are synthetic, but we had a listener, so we thought you’d want to use them)
  • NumberPicker android:value
  • RadioGroup android:checkedButton
  • RatingBar android:rating
  • SeekBar android:progress
  • TabHost android:currentTab (you probably don’t care, but we had the listener)
  • TextView android:text
  • TimePicker android:hour, android:minute (again, synthetic, but we had the listener)

You’re also going to start getting warnings now if your variable names collide with the View id’s in your layout. How do we know whether you mean the View or the variable in your expressions?

Rolling Your Own

Let’s imagine that the attribute you care about isn’t one of those listed above. How do you go about making your own? Let’s imagine that you have a color picker View where you want to have two-way binding for the color choice.

public class ColorPicker extends View {
    public void setColor(int color) { /* ... */ }
    public int getColor() { /* ... */ }
    public void setOnColorChangeListener(OnColorChangeListener listener) { 
        /*...*/
    }
    public interface OnColorChangeListener {
        void onColorChange(ColorPicker view, int color);
    }
}

The important aspect of the above View is that it has a listener that the data binding framework can listen for. Now we need to tell data binding about the attribute. The simplest way is using an InverseBindingMethod attribute on any class:

@InverseBindingMethods({
  @InverseBindingMethod(type = ColorPicker.class, attribute = "color"),
})

In this case, the name of the getter matches the name of the attribute “getColor” for “app:color.” If the name is something different, you can supply a method attribute to correct that. This creates a synthetic attribute to be used when binding the event using the name of the attribute with the suffix “AttrChanged.” Let’s see how this is used to set up the listener:

@BindingAdapter("colorAttrChanged")
public static void setColorListener(ColorPicker view,
        final InverseBindingListener colorChange) {
    if (colorChange == null) {
        view.setOnColorChangeListener(null);
    } else {
        view.setOnColorChangeListener(new OnColorChangeListener() {
            @Override
            public void onColorChange(ColorView view, int color) {
                colorChange.onChange();
            }
        });
    }
}

That’s the simplest kind of BindingAdapter — it converts between the color change listener type and the one used by the data binding framework, InverseBindingListener. However, you’re often going to want to also support binding to the colorChange event as well, right? You’re going to have to combine listeners:

@BindingAdapter(value = {"onColorChange", "colorAttrChanged"}, 
                requireAll = false)
public static void setColorListener(ColorPicker view,
        final OnColorChangeListener listener,
        final InverseBindingListener colorChange) {
    if (colorChange == null) {
        view.setOnColorChangeListener(listener);
    } else {
        view.setOnColorChangeListener(new OnColorChangeListener() {
            @Override
            public void onColorChange(ColorView view, int color) {
                if (listener != null) {
                    listener.onColorChange(view, color);
                }
                colorChange.onChange();
            }
        });
    }
}

Now you can use two-way data binding and bind to the onColorChange event, even in the same View.

InverseBindingAdapters

Similar to BindingAdapters and setters, there are times when you have to do something other than just calling a getter to retrieve a value. For example, if our ColorPicker has an enumeration of only a few colors, we may need to convert to int:

@InverseBindingAdapter(attribute = "color")
public static int getColorInt(ColorPicker view) {
    return ConvertColorEnumToInt(view.getColor());
}

Preventing Loops

One problem we encounter with two-way data binding is a sort of infinite loop. When making a change to a value raises an event, the listener will set the value on the target object. This can raise another event and it may trigger a change to the View again and we’re off on our loop. This kind of loop is normally caught either in the View’s setter or the data value’s setter, but this can’t be guaranteed if you don’t have control over the View or the data value. We’ve decided to prevent loops through BindingAdapters. For our ColorPicker example:

@BindingAdapter("color")
public static void setColor(ColorPicker view, int color) {
    if (color != view.getColor()) {
        view.setColor(color);
    }
}

There! No more loop.

I hope you get some use out of two-way data binding and let me know if you have any issues.

Next time I’ll talk about event lambda expressions that we released as well.

 

Advertisements

76 thoughts on “2-way Data Binding on Android!

  1. Great post! I am big fan of data binding and it’s great to see that new features are coming!
    I have a question, can we define custom binding from an EditText text to something that’s not a String?
    I am using an ObservableString class https://github.com/coding-jam/CoseNonJavisteAndroidApp/blob/master/app/src/main/java/it/cosenonjaviste/core/utils/ObservableString.java instead of an ObservableField, can I use this class in a two way data binding?
    I am using an ObservableString because the ObservableField.set method use == to compare objects (and I want to use equals to compare strings!) and ObservableString can declare a Parcelable implementation using readString and writeString.

    • Yes you can. The easiest solution is to use the ObservableField or ObservableString and then transfer the value to the final type later. But that may not be as interactive as you like. For example, if you want to convert a string to color and color to string, you’re going to need your own BindingAdapter and InverseBindingAdapter.

      Coming in a later release, we have a way to automatically convert between String and primitive types as a shortcut — you simply use the expression “@={“ + primitveValue}” but that’s not in the released version.

  2. Great additions to the Data Binding Library!
    I was trying to use the Implicit Attribute Listeners, but it does not seem to work for me (I am using Android Gradle Plugin 2.1.0-alpha4 in Android Studio 2.1.0-preview4). I am trying to do something similar to the example in this blog post: show/hide a LinearLayout based on the checked state of a CheckBox. But when I run my application, I get the following error: “Error:(124, 29) Identifiers must have user defined types from the XML file. receive_by_email is missing it”, where receive_by_email is the id of my CheckBox. Am I missing something?

    • I’m not sure why we did this (bug?) but the IDs of view are converted from underscores to Pascal case. So, look for an ID of receiveByEmail instead of receive_by_email.

      • You are right. receiveByEmail if is indeed working. However, my layout visibility is not changing when I check/uncheck my CheckBox. It is only correct for the initial state. Is there some other setting to do?

        About “How do we know whether you mean the View or the variable in your expressions?”. Why not use something like @{myVar.check} for variables and @{@id/myView.checked} for layout ids? It’s a little more verbose for ids, but it may prevents name conflicts.

  3. Views have entries in the generated Binding. It is just unfortunate that we named them with the Pascal case when it would have been perfectly fine to name them with the ID you set aside. We could have used the @id/view, but it looked pretty cumbersome. In any case, it is probably better to avoid reusing names for variables and view IDs.

    Can you upload a bug on the visibility problem? I have not seen that locally or in my unit tests. CheckBox’s checked attribute is one that is supported, so I don’t expect errors.

  4. Hey George,

    Thanks for article and this nice feature. I’ve tried to integrate 2 way data binding as you described. So I downloaded Android Studio 2.1 RC and set gradle to “classpath ‘com.android.tools.build:gradle:2.1.0-rc1′” but still can’t make it work. The error I am getting is:
    ‘Error:(26, 35) No resource type specified (at ‘text’ with value ‘@={user.firstName}’).’

    Should we make any additional configurations to make it work?

    Thanks

    • Shot in the dark: did you enable data binding in the gradle file? You need to add dataBinding.endabled=true in your android section of the app gradle.

      The other possibility is that you aren’t using an outer <layout> tag in your layout file.

      • Thanks for your answer George,

        Got it working now. Here what I did:
        1. First of all Android Studio 2.1 has just been released, so I upgraded it
        2. In my gradle file I was using:
        classpath “com.android.databinding:dataBinder:1.+”
        and in app gradle:
        apply plugin: ‘com.android.databinding’

        So I remove these two lines and
        dataBinding {
        enabled = true
        }
        instead.
        3. With the latest updates I also had to update my dagger to 2.2 as it was giving some conflicts with data binding.

        Don’t forget to provide setters in your view model

        Happy coding everyone!

  5. Thanks for article.
    I update the android studio to 2.1 and update the gradle to 2.1.0.
    But when I try to “Make Project”,It toast me that
    “cannot generate view binders java.util.NoSuchElementException”.
    If I use gradle 2.0.0 , “Make Project” worked.

    PS:
    The project is allready used Databinding and in gradle 2.0.0,it worked well.

    • Hmm… I’m not familiar with that error. If it worked with android gradle plugin 2.0.0, it should also work with android gradle plugin 2.1.0. Can you reproduce it with a small project and file the bug? We’d love to fix any problem like this.

      Thanks!

  6. Hi,

    I’m using Android Studio 2.1.1 (#AI 143.2821654). I get no errors but It still doesn’t work. What’s wrong in my code?

    PS: There is an official documentation page regarding the two-way binding?

    Thanks,
    Andrea

    Activity code:
    ——————————————————————————
    public class BuildingPlantActivity extends AppCompatActivity {

    BuildingPlantModel model;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ActivityBuildingPlantBinding binding =
    DataBindingUtil.setContentView(this, R.layout.activity_building_plant);
    model = new BuildingPlantModel(“test”);
    binding.setModel(model);

    setContentView(R.layout.activity_building_plant);
    }
    }

    Model code:
    ——————————————————————————
    public class BuildingPlantModel extends BaseObservable {

    public BuildingPlantModel(String name) {
    this.name = name;
    }

    private String name;

    @Bindable
    public String getName() {
    return this.name;
    }

    public void setName(String name) {
    this.name = name;
    notifyPropertyChanged(it.techgest.airetcc2.BR.name);
    }
    }

    Activity XML:
    ——————————————————————————

    Gradle (Project):
    ——————————————————————————
    buildscript {
    repositories {
    jcenter()
    }
    dependencies {
    classpath ‘com.android.tools.build:gradle:2.1.0’

    // NOTE: Do not place your application dependencies here; they belong
    // in the individual module build.gradle files
    }

    }

    allprojects {
    repositories {
    jcenter()
    }
    }

    task clean(type: Delete) {
    delete rootProject.buildDir
    }

    Gradle (app):
    ——————————————————————————
    apply plugin: ‘com.android.application’

    android {
    compileSdkVersion 23
    buildToolsVersion “23.0.3”

    defaultConfig {
    applicationId “it.techgest.airetcc2”
    minSdkVersion 15
    targetSdkVersion 23
    versionCode 1
    versionName “1.0”
    }
    buildTypes {
    release {
    minifyEnabled false
    proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
    }
    }
    dataBinding.enabled = true
    }

    dependencies {
    compile fileTree(dir: ‘libs’, include: [‘*.jar’])
    testCompile ‘junit:junit:4.12’
    compile ‘com.android.support:appcompat-v7:23.4.0’
    }

  7. Hi, I’m using Android Stuido 2.1.1 and I’m getting an error when trying to use 2-way databinding on the properties of a class binding them to the checked property.

    This is the error:
    ****/ data binding error ****msg:The expression output.nResolved cannot cannot be inverted: Two-way binding cannot resolve a setter for boolean property ‘nResolved’

    public class Output{
    public boolean nAdditionalDispatch;
    public boolean nNoDispatch;
    public boolean nInitialContact;
    public boolean nVendorContact;
    public boolean nResolved;
    }

    I’ve only tried hooking up one of them like
    android:checked=”@={output.nResolved}” for a CheckBox

    The object was in the xml as output

    I also get this issue on strings, however it seems to work if you add both getters and setters to the class.

    There is no problem when binding directly to a Boolean object declared in the xml.

    Is this supposed to be how it works?

  8. Hi, when i use only Two-Way Data Binding or only Implicit Attribute Listeners they works fine.
    Is it possible to add Two-Way Data Binding and Implicit Attribute Listeners on same EditText?
    when i do:

    i got error:
    Error:Execution failed for task ‘:app:compileDebugJavaWithJavac’.
    > java.lang.RuntimeException: Found data binding errors.
    ****/ data binding error ****msg:Cannot find the setter for attribute ‘android:textAttrChanged’ with parameter type android.databinding.InverseBindingListener on android.widget.EditText.

      • I think wordpress eats XML. You have to use &lt; instead of <.

        This may be related to another bug. Are you using libraries that also use data binding? If so, there appears to be a bug where if some libraries are compiled with older versions then it can’t properly resolve some of the binding adapters.

  9. Hi, when i use on the same view only Two-Way Data Binding or Implicit Attribute Listeners all works fine. But when i try to add both of them on the same EditText:

    i got error:
    Error:Execution failed for task ‘:app:compileDebugJavaWithJavac’.
    > java.lang.RuntimeException: Found data binding errors.
    ****/ data binding error ****msg:Cannot find the setter for attribute ‘android:textAttrChanged’ with parameter type android.databinding.InverseBindingListener on android.widget.EditText.
    ****\ data binding error ****

    • it was EditText with android:text=”@={viewModel.user}” and id=”@+id/user”, inside InputTextLayout with app:error=”@{user.text.toString().isEmty() ? @string/error : @string/no_error”

      • ok, i found right way for my case. in app:error instead user.text i use viewModel.user and it works well. Thanks for great work George!

  10. Hi,
    Thank you for the great tutorial.

    One thing I was hoping to clarify was two-way data binding on Spinners. Essentially, I am able to use Data Binding to set the Spinner’s default selection, I do this by adding binding to the Spinner’s xml as follows:

    It’s quite easy to set up the original selection, however, how can I return the user’s subsequent selection using Data Binding? I can find no attribute to bind to for the resulting spinner’s selection, nor can I figure out a good custom approach using a BindingAdapter. If you could please push me in the right direction with Spinner’s specifically that would help tremendously!

    Thank you!

    • Your XML was erased by WordPress. You’ll need to use &lt; to escape it.

      I imagine it was something like this:

      <Spinner android:selection=”@{data.selected}” >

      Why don’t I follow up with a HOWTO for Spinner?

      • You’re correct. Another tutorial would be invaluable, and if you could just drop me a hint in the meantime I’d very much appreciate it. Trying to turn all views into MVVM and spinner is holding me up!

        PS this is the ONLY source I could find that demonstrates two way binding. There’s not even official guidance on this, how’s that possible?

      • Sorry I was under the impression I tried that attribute (having seen it in your table above). Perhaps the IDE hiccuped, I’ll have to go back and see why.

        Thank you very much, with spinners tied in I have zero boilerplate code for views! Plus I have a very simple MVVM structure!

        Data binding has made coding enjoyable again! Thank you so much for this, the boilerplate was distracting me from business logic aka the fun parts!

      • Sorry, I’m embarrassed, but I can’t figure out how to implement this attribute in a Spinner, in fact, this attribute doesn’t even exist if I do a Google search for “android:selectedItemPosition”. Forgive my ignorance please, how do I implement this with two-way binding?

        (PS: Google account linkage would not work)

      • Hi George,
        “android:selectedItemPosition” does not exist. I am sure you’re more knowledgeable than me, but I can’t find a trace of this attribute. Is there something I’m missing?

        Perhaps inherent in your discussion is the fact that I need to create a custom binding adapter and inverse binding adapter for the methods getSelectedItemPosition() and setSelection()?

        I really appreciate it!

      • There appears to be a bug in finding the getter for the android:seelctedItemPosition. I think this is due to Spinner extending a specific implementation of AdapterView (AdapterView<SpinnerAdapter>)

        You can work around it by implementing it yourself until I fix it:

        @InverseBindingMethods({
        @InverseBindingMethod(type = Spinner.class, attribute = “android:selectedItemPosition”),
        })

        This can be on any class, though I recommend one that you are using for BindingAdapters, anyway. In my test, I did it on my Activity (I know, poor form, but it was only a test). My apologies!

      • Thanks George, I figured it was probably some typo, but this explains it.

        If it weren’t for Android data binding I would be up to my neck in unmanageable boilerplate code, thanks so much for your contributions

  11. How can I accomplish a simple two-way Integer databinding on TextInputEditText with text=”@={ViewModel.someInt}”. I have tried BindingAdapter and InverseBindingAdapter on text attribute with no luck.

  12. This is a great article, can’t wait for the full documentation to be available. I’ve tried this out, and it works quite well for me so far. Though, I have some need to know when two-way databound classes have been updated so that my code can update some related values on a different part of the screen. How would I detect that these changes were made? Do I have to set up my own listener for these situations, or is there something built-in to the Databinding framework. The class I am updating inherits from BaseObservable, but strange things happen if I do any notifyPropertyChanged in my setters (obviously). Thanks for any insight.

    • Look at the section on implicit attribute listeners. You can bind an attribute to any attribute that supports two-way binding like this:

      <ImageView android:visibility="@{myCheckBox.checked ? View.VISIBLE : View.GONE}" .../>

      You could also do something with your own attribute if you want to set up your own binding adapter or have a setter for it on a custom View:

      <ImageView app:myAttr="@{myCheckBox.checked}" .../>

      You could also be notified directly with a listener:

      <CheckBox android:onChecked="@={data.checked}" android:onCheckedChanged="@{handlers::checkChanged}" .../>

      Setting up your own BindingAdapter may be a bit more challenging. You’ll have to duplicate the BindingAdapter for the attribute you want to copy, but target the app namespace.

  13. Hi George,
    I’ve been loving data-binding since you set me straight on Spinners. I am currently facing a struggle with how to implement the following scenario:

    I have 4 Spinners, these Spinners are for selecting a vehicle, the first Spinner is Year, second is Make, third is Model and fourth is Sub-Model. An example of a completed selection would be 2008 -> Honda -> Civic -> Si.

    I’ve attempted to implement this using ObservableArrayLists to hold each Spinner’s contents, as well as using two-day databinding on the attribute “android:selectedItemPosition”. Essentially how it works is the class loads the years into the Year Spinner which then triggers a cascading effect of the setters for each Spinner requesting the next Spinner’s contents.

    Hopefully seeing the implementation will show you what I am attempting to do:

    <Spinner
    android:id=”@+id/spinner_years”
    android:entries=”@{viewModel.getArrayYears}”
    android:selectedItemPosition=”@={viewModel.year}”
    android:layout_width=”wrap_content”
    android:layout_height=”wrap_content”
    android:layout_below=”@id/tv_car_pick”
    android:layout_toRightOf=”@+id/icon_zero_items”/>

    <Spinner
    android:id=”@+id/spinner_makes”
    android:entries=”@{viewModel.mListMakes}”
    android:selectedItemPosition=”@={viewModel.make}”
    android:layout_width=”wrap_content”
    android:layout_height=”wrap_content”
    android:layout_below=”@id/spinner_years”
    android:layout_toRightOf=”@+id/icon_zero_items”/>

    <Spinner
    android:id=”@+id/spinner_models”
    android:entries=”@{viewModel.mListModels}”
    android:selectedItemPosition=”@={viewModel.model}”
    android:layout_width=”wrap_content”
    android:layout_height=”wrap_content”
    android:layout_below=”@id/spinner_makes”
    android:layout_toRightOf=”@+id/icon_zero_items”/>

    <Spinner
    android:id=”@+id/spinner_styles”
    android:selectedItemPosition=”@={viewModel.style}”
    android:entries=”@{viewModel.mListStyles}”
    android:layout_width=”wrap_content”
    android:layout_height=”wrap_content”
    android:layout_below=”@id/spinner_models”
    android:layout_toRightOf=”@+id/icon_zero_items”/>

    The problem:

    Obviously the selection in “parent” Spinner imparts the choices available in the “child” Spinner, so if you select Honda as a Make then Civic will be a Model in the Spinner below (as well as Accord, Pilot, etc). The problem is that the logic only cascades properly upon loading the activity the first time, if I select a new Year for instance, the Spinners below Year do not get triggered. I believe this is because the position is not changing in the Spinner, and thus the call to onItemSelected() is not occurring.

    While I can remedy this issue by simply not using data-binding and re-loading the Spinner with a new adapter, I would like to use data-binding exclusively for my UI so as not to over-complicate things with multiple approaches.

    I would greatly appreciate your advice on this topic. I greatly appreciate the assistance!

    • I wasn’t able to reproduce this problem. Are you replacing the lists when the values change? I used an Observable model class to hold the data when I tried it and replaced the list data when the value changed:

      public class Data extends BaseObservable {
          private int makePosition = 0;
      
          @Bindable
          public int getMakePosition() {
              return makePosition;
          }
      
          public void setMakePosition(int position) {
              makePosition = position;
              notifyPropertyChanged(BR.makePosition);
              replaceList();
          }
      }
      

      You can also do it by listening to an ObservableField:

      public class Data {
          public final ObservableInt makePosition = new ObservableInt();
      
          public Data() {
              makePosition.addOnPropertyChangedCallback(new OnPropertyChangedCallback() {
                  @Override
                  public void onPropertyChanged(Observable observable, int i) {
                      replaceList();
                  }
              });
          }
      }
      

      You can also do it with lists of lists — they don’t even need to be ObservableLists as they are static.

      <Spinner android:entries="@{viewModel.makes}"
          android:selectedItemPosition="@={viewModel.makePosition}" .../>
      <Spinner android:entries="@{viewModel.models[viewModel.makePosition]}"
          android:selectedItemPosition="@={viewModel.modelPosition}" .../>
      <Spinner android:entries="@{viewModel.styles[viewModel.makePosition][viewModel.modelPosition]}"
          android:selectedItemPosition="@={viewModel.stylePosition}" .../>
      

      Of course, if your lists are huge, this won’t fit well into memory, but for cars it may be fine. You may also want to use dynamic loading of the resources and that would just use a method rather than brackets.

      • Thanks George for the ideas. I’ll solve this tonight and report back my findings in case others wonder how cascading Spinners using data-bimding.

      • I feel like a bit of a fool trying to get this aspect working. I can’t wrap my head around the fact it works on load, but never subsequently works again..

        Perhaps the reason is because I’m clearing the ObservableArrayList using .clear() and then using .addAll to add the updated results? When you say replace the list, I’m certain I’m not doing that, could it explain why my setters aren’t being called in a cascading fashion, or no?

        Sorry for the ultra newb, I feel a bit out of my element here.

  14. Here’s my class when using the more complex expression:

    android:entries="@{data.models[data.makePosition]}"
    
    public class Data {
    
        public final ObservableList makes = new ObservableArrayList();
        public final ObservableList<ObservableList> models = new ObservableArrayList();
        public final ObservableInt makePosition = new ObservableInt();
    
        public static final String[] MAKE_LIST = {
                "select make",
                "Honda",
                "Tesla"
        };
    
        public static final String[][] MODELS_LIST = {{
                "select make first"
        }, {
                "Accord",
                "Civic",
                "HR-V",
                "CR-V",
                "Pilot",
                "CR-Z",
                "Odyssey",
                "Crosstour",
                "Ridgeline",
        }, {
    
                "Roadster",
                "Model S",
                "Model X",
                "Model 3",
        }};
    
        public Data() {
            replace(makes, MAKE_LIST);
            for (String[] list : MODELS_LIST) {
                ObservableList<String> vals = new ObservableArrayList();
                replace(vals, list);
                models.add(vals);
            }
        }
    
        private static void replace(List<String> target, String[] newContents) {
            target.clear();
            for (String value : newContents) {
                target.add(value);
            }
        }
    }
    

    This is what I used when I tried replacing the list after selection:

    android:entries="@{data.models}"
    
    public class Data extends BaseObservable{
    
        public static final String[] MAKE_LIST = {
                "select make",
                "Honda",
                "Tesla"
        };
    
        public static final String[][] MODELS_LIST = {{
                "select make first"
        }, {
                "Accord",
                "Civic",
                "HR-V",
                "CR-V",
                "Pilot",
                "CR-Z",
                "Odyssey",
                "Crosstour",
                "Ridgeline",
        }, {
    
                "Roadster",
                "Model S",
                "Model X",
                "Model 3",
        }};
    
        public final ObservableList<String> makes = new ObservableArrayList();
        public final ObservableList<String> models = new ObservableArrayList();
        private int makePosition = 0;
    
        public Data() {
            replace(makes, MAKE_LIST);
            replaceModels();
        }
    
        private static void replace(List<String> target, String[] newContents) {
            target.clear();
            for (String value : newContents) {
                target.add(value);
            }
        }
    
        void replaceModels() {
            replace(models, MODELS_LIST[makePosition]);
        }
    
        @Bindable
        public int getMakePosition() {
            return makePosition;
        }
    
        public void setMakePosition(int position) {
            makePosition = position;
            notifyPropertyChanged(com.example.mount.testtwowayspinner.BR.makePosition);
            replaceModels();
        }
    }
    

    Hope that helps.

  15. Hi George,
    Problem:
    As BindingAdapters require static methods, and as static methods do not take type parameters, it appears impossible to create a BindingAdapter that can facilitate inputs that are “generically typed” (I might have this wording wrong).

    Explanation:
    In my app I have an abstract class “ShortListViewModel” which holds about 90% of the code for each concrete ShortList class I create from it. A ShortList is just a LinearLayout which has a max of 5 child views added to it (that’s why it’s short). An example of a ShortList is a list of 5 car parts, or a list of 5 fuel receipts, the idea behind a ShortList is to show a small subset of items and the user can click through to expand to a full list of items shown in chronological order.

    Because every ShortList has to have a collection of items which can change, I created a public observable field in ShortListViewModel as such:

    public final ObservableArrayList<T> listData = new ObservableArrayList<>(), where T extends DaoBase (car parts, fuel receipt objects, etc. extend DaoBase).

    In my XML layout I wire this up as follows:

    <layout>

    <variable name=”viewModel” type=”com….ShortListViewModel”/>

    <LinearLayout

    bind:listData=”@{viewModel.listData}”

    />
    ….
    </layout>

    The issue I face is that it’s not possible to use a type parameter in a static method, as such I can never satisfy the compiler as it’s going to be looking for a method that takes ObservableArrayList<T> as an input which won’t compile as follows:

    @BindingAdapter(“bind:listData”)
    public static void drawList(LinearLayout view, ObservableArrayList<T> listData) {
    // This method can’t exist as it uses a type parameter and not the type itself as required for a static method
    }

    The only solution I’ve been able to come up with is to include the ObservableArrayList in each concrete implementation and create a separate XML layout for each ShortList. Then in each XML layout I set the variable to be the concrete implementation of ShortList over ShortListViewModel (the abstract class).

    I’m open to criticism on my approach, I’d greatly appreciate it.

  16. You can use type parameters in static methods:

    @BindingAdapter(“bind:listData”)
    public static <T> void drawList(LinearLayout view, ObservableArrayList<T> listData) {
    //…
    }

  17. Hi, George,

    Thanks for the great article!
    After reading it I wanted to give it a try but encountered a problem regarding loops.

    I have a form that the user can fill, it has two editTexts (price, price including VAT) and a spinner to select VAT percent (default value being 24%). I want to present prices with two decimal digits.
    When the user enters the other price field the other is supposed to be updated automatically. Let’s say the user enters value 10 to price incl. VAT, the price value 8,06 is calculated and the UI is updated (great!).

    Now, that the price value is updated I get event and calculate price including VAT from that but because price is not exactly 8,06 (but 8,06451…) the result is 9,99 which is updated to the UI (bad). I’m preventing loops by checking if the value is the same as current value and only send event if the current and old value don’t match but that’s not helping here.

    Is there a nice way to react only to the original event? i.e. if the price incl. VAT has changed I want to update UI with the corresponding price value but I don’t want to process price value changed event in my viewModel (inherits BaseObservable).

    • This is a tricky problem. I’m going to assume that price with VAT is essentially a calculated value and you don’t want to rely entirely on it. I also assume that you also don’t really collect anything smaller than a 1/100th of a unit (dollar, euro, pound, whatever). I recommend that if the user types a value directly into the Price with VAT field that you recalculate that field after the user leaves it so that the price is a number rounded to the penny. This technique should also work if price is the calculated field or you can collect fractions.

      This is what I came up with for the model:

      public class Purchase extends BaseObservable {
      private float mPriceWithVat = 124f;
      private float mPrice = 100f;
      private float mVat = 24f;
      private boolean mVatFocused;

      @Bindable
      public float getPriceWithVat() {
      return mPriceWithVat;
      }

      public void setPriceWithVat(float priceWithVat) {
      mPriceWithVat = priceWithVat;
      float priceMultiplier = 1f + (mVat / 100f);

      // Make the price an even cent
      mPrice = Math.round(priceWithVat / priceMultiplier * 100f) / 100f;

      notifyPropertyChanged(BR.priceWithVat);
      notifyPropertyChanged(BR.price);
      }

      @Bindable
      public float getPrice() {
      return mPrice;
      }

      public void setPrice(float price) {
      float priceMultiplier = 1f + (mVat / 100f);

      // Make the price an even cent
      mPrice = Math.round(price * 100f) / 100f;

      // Now recalculate the price with VAT
      float calculatedWithVat = Math.round(mPrice * priceMultiplier * 100f) / 100f;

      mPriceWithVat = calculatedWithVat;
      notifyPropertyChanged(BR.price);
      if (!mVatFocused) {
      notifyPropertyChanged(BR.priceWithVat);
      }
      }

      @Bindable
      public float getVat() {
      return mVat;
      }

      public void setVat(float vat) {
      mVat = vat;
      float priceMultiplier = 1f + (mVat / 100f);

      // Now recalculate the price with VAT
      float calculatedWithVat = Math.round(mPrice * priceMultiplier * 100f) / 100f;

      mPriceWithVat = calculatedWithVat;
      notifyPropertyChanged(BR.priceWithVat);
      notifyPropertyChanged(BR.vat);
      }

      public void priceWithVatFocusChange(View v, boolean hasFocus) {
      mVatFocused = hasFocus;
      if (!hasFocus) {
      notifyPropertyChanged(BR.priceWithVat);
      }
      }
      }

      Here, I’ve assumed you use two-way binding on the float properties. I know that you’ll want to use non-floats for monetary units, but I wanted to keep it simple.

      The only special thing I added was a focus change binding on the “price with vat” edit text:


      android:onFocusChange="@{purchase::priceWithVatFocusChange}"

  18. Hi George, great article!

    But, I got an error that came from the generated file when i was implementing this.
    First of all i have something like this:

    then, this is the view model

    public class BookingDataViewModel extends BaseObservable {
    private String helpText;

    public void setHelpText(String helpText) {
    this.helpText = helpText;
    notifyPropertyChanged(BR.buttonCloseHelpVisible);
    notifyPropertyChanged(BR.validMessage);
    notifyPropertyChanged(BR.validMessageCount);
    }

    @Bindable
    public String getHelpText() {
    return helpText;
    }

    }

    and it is used by an activity called `BookingCompleteActivity`

    Everything worked fine before i tried to implement 2 way databinding on edittext. The IDE keeps telling me that the error lays on `ActivityBookingCompleteBinding.java`, which is automatically generated. Inside this part:

    private android.databinding.InverseBindingListener inputHelpandroidText = new android.databinding.InverseBindingListener() {

    @Override
    public void onChange() {
    //some lines here
    }

    }

    the BookingViewModel is not initialized correctly. There is an incompatible error in this line:

    bookingDataViewModel = (bookingDataViewModel) != (null);

    where the required `bookingDataViewModel` type is object BookingViewModel, but it is found as a boolean. Could you let me know why this happens?

    Thanks

  19. Hello George. Thanks for sharing your wisdom. I decide to implement workable example based on your article and put it on github to simplify understanding of the whole concept for myself and other folks. But my code isn’t working as expected and I’m not sure I fully understand why. I will be very appreciate if you will find a little time to take a look.

    Link to gihub repo: https://github.com/centaurea/Android_2way_databinding_example

    And I also asked question on stackoverflow about that: http://stackoverflow.com/q/38907796/1888017

  20. Hi,
    I now use the two-way data binding in my code and it works really great. But there is on problem I don’t know to handle. I have an EditText view where the android:text attribute is set with data binding:
    android:text=”@={shoe.kind}”

    The model has this field:
    ObservableField kind = new ObservableField();

    I want to use this string to enable/disable a button in the same layout file so I set the button attribute “enabled” to:
    android:enabled=”@{shoe.kind.empty}”

    But the test shows that the button gets never enabled when I type text in my EditText.
    When I delete the enabled attribute and log the text of the EditText in the console on button click it shows exactly the text I typed.

    Do I have to add some more logical methods in my Model or does the enabled attribute is not supported on listening for field changes?

    • When I first thought about it, I thought “of course it won’t work, strings aren’t observable.” But then I realized that the ObservableField itself changes when the text changes, so the entire expression “shoe.kind.empty” should be reevaluated. So, I thought I’d try it out. And it works! I believe that you have your logic reversed in your enabled field. It should be:

      android:enabled=”@{!shoe.kind.empty}”

      • Oh my gosh, thank you very much. That´s a bad mistake I made. Now it works.
        Thank you again for this great article.

  21. Hi George!
    Thanks for the great article:)
    I have a custom checkbox (which will align the drawable on top if the text has multiple lines; extends FrameLayout). I would like to set the other views visibility based on the custom checkbox’s checked state. Is this possible? I’ve tried to implement this based on the Implicit Attribute Listeners part, but it didn’t worked for me. Says that “Cannot find setter for attribute checkedAttrChanged with parameter InverseBindingListener”. Can you help me?

    Thanks in advance,
    Tamas

    • I tried it out with the android.widget.CheckBox and I got the same error. The reason is that I have some BindingAdapters(android:layout_marginLeft, app:typeface in this case) set for the CheckBox. If I remove those, everything is working just fine, the other views in the layout are updated based on the CheckBox’s checked state. Is this a bug?
      I tried with my custom checkbox and it’s working too without any other bindingAdapters set. If I add the layoutMargin or typeface, I get the same error: “Error:(87, 9) Cannot find the setter for attribute ‘checkedAttrChanged’ with parameter type android.databinding.InverseBindingListener on …TopAlignedCheckBox. “

    • The likely problem is that you’re trying to use the android:checked attribute in the expression directly like this:

      android:visibility="@{checkbox.checked}"

      But visibility is a 3-state integer, not a boolean, so it can’t convert it. Instead, use a ternary expression:

      android:visibility="@{checkbox.checked ? View.VISIBLE : View.GONE}"

      • Thanks for your answer!

        I’ve tried it that way too without any success. Anyway I have a BindingConverter which converts boolean to visibility, and it’s working in other layouts. So the problem still persists for me: if I use other binding adapters on the CheckBox, it isn’t working:(

  22. Is it necessary to use a custom layout? I don’t have that and I’m getting “package com.mypackage.databinding does not exist” when I do the two-way binding tag like @={object.property}. With one way binding, @{object.property}, it works fine.

    • If one-way binding works, then two-way binding should also work. If you are having trouble, put additional info here or ask on Stack Overflow. Stack Overflow is a little better because the code viewing is so much better.

  23. This is a great stuff, i’ve read it a few times now and the Data Binding library doc from developer.android.com. What I can’t get my head around is how to retrieve fields from the model in the activity? For instance, the user enters text in a form and then in the activity code I want to get the model and do something with the fields within (output it to the console would be sufficient for now. In my activity I have
    ActivityCreateAccountBinding binding = ActivityCreateAccountBinding.inflate(getLayoutInflater());
    Account acc = binding.getAccount();
    System.out.println(acc.getUsername() + ” : username”);
    All I get is null pointer exceptions.

    Have I got the syntax wrong or have I got the concept of Android 2 way data binding incorrect.
    I have got some binding working because if I have initialised a field in the model it will update the text field on creation. Thanks muchly 🙂

    • You are responsible for setting the data objects in the binding:


      ActivityCreateAccountBinding binding = ActivityCreateAccountBinding.inflate(getLayoutInflater());
      Account acc = new Account();
      binding.setAccount(acc);

      • Thanks for the prompt response ! I hasn’t worked as expected unfortunately.

        I have implemented this in onCreate and made acc its own field so that I can reference it later.

        My layout has
        ….

        …..
        And Account has

        public class Account {

        private String username = “Isaac”;
        private String password;
        private String first_name;
        private String last_name;
        private String email;
        private String phone;

        public String getUsername() {
        return username;
        }

        public void setUsername(String username) {
        this.username = username;
        }
        …….
        …..
        The view does not show Isaac in the field on creation.

        In my layout I have a “Sign Up” button that calls a signUp method. Here is where I have in that method:

        acc.getAccount();

        System.out.println(acc.getUsername());

        This prints Isaac but not any values that I put in there.
        Any other help would be great thanks.

  24. I just tried it and it worked fine for me:

    public class Account {
    private String username = "Isaac";
    private String password;
    private String first_name;
    private String last_name;
    private String email;
    private String phone;

    public String getUsername() {
    return username;
    }

    public String getPassword() {
    return password;
    }

    public void setPassword(String password) {
    this.password = password;
    }

    public String getFirstName() {
    return first_name;
    }

    public void setFirstName(String first_name) {
    this.first_name = first_name;
    }

    public String getLastName() {
    return last_name;
    }

    public void setLastName(String last_name) {
    this.last_name = last_name;
    }

    public String getEmail() {
    return email;
    }

    public void setEmail(String email) {
    this.email = email;
    }

    public String getPhone() {
    return phone;
    }

    public void setPhone(String phone) {
    this.phone = phone;
    }

    public void setUsername(String username) {
    this.username = username;
    }
    }

    And the activity:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    AccountBinding binding = DataBindingUtil.setContentView(this, R.layout.account);
    Account account = new Account();
    binding.setAccount(account);
    binding.setHandlers(new EventHandler());
    }

    public static class EventHandler {
    public void onAccountClick(Account account) {
    System.out.println("User name: " + account.getUsername());
    System.out.println("email: " + account.getEmail());
    }
    }

    and the layout:

    <EditText
    android:id="@+id/username"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@={account.username}"/>

    • That was also helpful, I need the handlers variable in my xml. I opted to use
      android:onClick=”@{() -> handler.signUp(account)}”.

      I had code missing rather than getting it wrong. Appreciate the help.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s