2

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

2 Answers 2

9

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])}" .../>
Sign up to request clarification or add additional context in comments.

2 Comments

>>> The first is that getPrice() returns an int instead of a CharSequence or String --- Ive already have declined methods for these conversions, but otherwise its really great and nice description! Thank you very much!
By the way, it is the right answer. Since when you change the model(besides the case where the model inherit from the BaseObservable and your class fields has @Bindable annotation with corresponding calls notifyChange() inside the model) - databinding does not know about your changes. So, Observables from the android.databinding package - is the real solution. Thanks, guys for your help!
4

Instead of writing your own binding methods in this case, consider using an ObservableMap.

public final ObservableMap<Object, Integer> price = new ObservableArrayMap<>();

<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.price[priceKey]}"/>

But the issue is with converting the text value (a CharSequence) to the mapped value (an int). For this you need the @InverseMethod. In your case such a method is not sufficient though. So you need to provide an @InverseBindingAdapter instead.

@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
public static Integer convertTextToInteger(EditText view) {
    String text = view.getText().toString();
    return text.isEmpty() ? null : Integer.parseInt(text);
}

Also you'll have to provide a @BindingConversion or a @BindingAdapter for mapping integers to CharSequence to set the text in the first place.

@BindingConversion
public static CharSequence convertMyClass(Integer i) {
    return i == null ? null : i.toString();
}

1 Comment

I have already declined @InverseBindingAdapter. It doesn`t help. Maybe ObservableMap will be the solution. I will check this and put a comment later. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.