Search code examples
androidandroid-databindinginversetwo-waytwo-way-binding

Android two-way databinding for methods with parameters


Setter and getter methods of the model have one parameter, like this:

public int getPrice(Object key) {
}

public void setPrice(Object key, int price) {
}

XML looks like this:

<EditText
android:id="@+id/edtPrice"
style="@style/CreateShipperItemValueEditTextView"
android:layout_centerVertical="true"
android:hint="@string/hint_price"
android:inputType="number"
android:maxLines="1"
android:text="@={shipper.getPrice(priceKey)}"/>

android:text="@={shipper.getPrice(priceKey)}"

Compiler errors during the building say that we should you use @InverseMethod annotation. I try something like this:

@InverseMethod(value = "getPrice")
@Override
public void setPrice(Object key, int price) {
    super.setPrice(key, price);
}

But in this case I have the next error.

error: @InverseMethods must have a non-void return type

So I will be glad to here nice explanation of the whole flow. Thanks


Solution

  • Nice to see you're using the new InverseMethod available in Android Studio 3.0.

    You're using two-way data binding with a method and the expression must understand how to set the value when the user modifies the text. The use is intended for conversion methods, so I don't know if this applies in your case.

    Essentially, data binding uses code like this when setting the text to the View:

    EditText edit = binding.edtPrice;
    edit.setText(shipper.getPrice(priceKey));
    

    And when the text changes, you are asking it to do this:

    EditText edit = binding.edtPrice;
    priceKey = shipper.setPrice(priceKey, edit.getText());
    

    That's clearly not going to work and it gives you an error.

    There are two things wrong, really. The first is that getPrice() returns an int instead of a CharSequence or String. The second is that setPrice() isn't really doing a conversion -- it takes a different number of parameters and returns a void.

    In your case, you're not trying to convert priceKey into an integer; you're trying to access a value in a map of some sort. There are a few ways to handle this. I think the easiest way is to use a map directly in your model. Here, I'll assume that you have a final field, but you could return it as a property using getters and setters:

    public class ShipperModel {
        public final ObservableArrayMap<Object, Integer> price = new ObservableArrayMap<>();
        //...
    }
    

    and then in your expression:

    <EditText android:text="@={`` + shipper.price[priceKey]}" .../>
    

    If you want custom conversions, you can do it like this:

    @InverseMethod("convertToPrice")
    public String convertFromPrice(Object key, int price) {
        //...
    }
    
    public int convertToPrice(Object key, String text) {
        //...
    }
    

    And then you would pass the price object in the expression:

    <EditText android:text="@={shipper.convertFromPrice(priceKey, shipper.price)}" .../>
    

    I would simplify the conversion methods to make them reusable throughout my application and just convert an integer to String and back:

    public class Converters {
      @InverseMethod("fromPrice")
      public static final int toPrice(String text) {
          //...
      }
    
      public static final String fromPrice(int price) {
        //...
      }
    }
    

    and then the expression would be:

    <EditText android:text="@={Converters.fromPrice(shipper.price[priceKey])}" .../>