47

I'm having some issue with implementing two way binding with an Integer data type.

public class User {

    private String firstName;
    private String lastName;
    private int age;

    public User() {}

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

    public String getFirstName() {
       return this.firstName;
    }

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

    public String getLastName() {
       return this.lastName;
    }

    public void setAge(int age) {
       this.age = age;
    }

    public int getAge() {
       return this.age;
    }

}

XML:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data class="UserDataBinding">
        <variable
            name="user"
            type="com.databinding.model.User" />
    </data>

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="@dimen/activity_horizontal_margin">

       <EditText android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={user.firstName}" />

       <EditText android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={user.lastName}" />

       <EditText android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={user.age}" />

    </LinearLayout>
</layout>

Unfortunately, it gives me the error

"Error:(52, 17) Cannot find the getter for attribute 'android:text' with value type java.lang.Integer on android.support.design.widget.TextInputEditText. "

If I change the attribute text to

       <EditText android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={Integer.toString(user.age)}" />

then I get the error

"Error:cannot generate view binders java.lang.NullPointerException"

Appreciate any help on this.

UPDATE: It seems there was another error right after the error mentioned above.

cannot generate view binders java.lang.NullPointerException

Not sure why its giving me NPE even though the app hasn't started yet.

ads
  • 1,703
  • 2
  • 18
  • 35
  • 1
    Maybe switching `Integer` to `int` might solve it because of automatic conversion from `String` to `int` or something. A hacky way would be to have the age stored as a String and then convert it. – Vucko Aug 17 '16 at 13:39
  • Changed Integer to int in my User object but still getting the same error. – ads Aug 17 '16 at 13:49
  • 1
    Try replacing the `Integer` with `ObservableInteger`. You will have to use the method `.set(SOMENUMBER)` then though. See [this post](http://stackoverflow.com/questions/37867988/android-data-binding-bindingconversion-failure-for-int-to-string) for reference. Dont forget to use the `Integer.toString()` method then – Rittel Aug 17 '16 at 14:11

9 Answers9

89

Well, six months later but maybe i can help someone.

You can do this simple trick:

android:text="@={`` + mObject.someNumber}"

OBS.: You need at least Android Studio 2.3

Eduvm
  • 1,131
  • 8
  • 14
  • 1
    I had to search hard to find this solution. It is by far the simplest and should be on top. Do you know where I can find documentation on this? – xerotolerant Mar 27 '17 at 21:51
  • Sorry @xerotolerant Like you, i didn't find any documentaion about this. – Eduvm Mar 29 '17 at 13:35
  • Does this work with, for example, double and int values at the same time? – Draško May 13 '17 at 13:22
  • Will this work for 2-way biniding? Will the typed string be converted into integer? – ArJ Jun 16 '17 at 14:01
  • 1
    Great tip! Thank you. Just in case some are like me, please note those symbols are back quotes (top left corner of your keyboard), not single quotes. – Hong Nov 17 '17 at 15:53
  • 4
    Note: this only works with primitive types, not e.g. BigDecimal. – Mikel Jan 17 '18 at 04:35
  • This should be the accepted answer, but as Hong just said, be aware that you can't use `'` or `"` inside the escaped code in XML, must use the backtick ` – barnacle.m Aug 21 '18 at 09:10
  • 1
    Undocumented, works only on some types, requires back-tick, fails for 2-way data-binding under these 7 conditions, resolved by writing `@BindingAdapters`, and/or custom converter methods or whole classes. Needs specific Studio version, new layout XML, `` or `` nodes depending on `@jvmStatic`, data-binding compiler for Kotlin, `androidx` libraries...My GOD. Android was already the worst SDK in history BEFORE google slapped a whole 'nuther SDK ON TOP of it. – rmirabelle May 19 '19 at 22:04
  • 1
    For me, it did't allow clearing value and entering. Last digit remains in edit – Tejasvi Hegde Dec 18 '19 at 11:04
  • Can you help me with this: https://stackoverflow.com/questions/64596538/android-two-way-databinding-problem-of-ternary-operator-must-be-constant Thank you. – Sam Chen Oct 29 '20 at 20:32
23

android:text="@{String.valueOf(Integer)}"

Nimdokai
  • 787
  • 1
  • 6
  • 18
15

Somehow I got this to work by using BindingAdapter and InverseBindingAdapter.

public class User {

    private String firstName;
    private String lastName;
    private int age;

    public User() {}

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

    public String getFirstName() {
       return this.firstName;
    }

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

    public String getLastName() {
       return this.lastName;
    }

    public void setAge(int age) {
       this.age = age;
    }

    public int getAge() {
       return this.age;
    }

    @BindingAdapter("android:text")
    public static void setText(TextView view, int value) {
        view.setText(Integer.toString(value));
    }

    @InverseBindingAdapter(attribute = "android:text")
    public static int getText(TextView view) {
        return Integer.parseInt(view.getText().toString());
    }
}

Hopefully this will help someone else as well.

ads
  • 1,703
  • 2
  • 18
  • 35
  • Hey, thanks for this solution but I still have a problem with it: every character that I type in the EditText reset the cursor at the beginning of the text. Do you experience the same issue? Any fix for this problem? – Roberto Leinardi Sep 21 '16 at 16:25
  • Ok, I've found a solution: set the new text only if the text of the `view` is different from the `value` in the `@BindingAdapter("android:text")`. – Roberto Leinardi Sep 21 '16 at 16:47
10

I managed to use Integer.toString(...), doing the import, like this:

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

    <data>

        <import type="java.lang.Integer" />

        <variable ... />
    </data>

7

The previous answer, along with Roberto Leinardi's comment worked perfectly for me! I only have to add is that a null-check should be done to Roberto's check:

@BindingAdapter("android:text")
public static void setText(TextView view, int value) {
    view.setText(Integer.toString(value));
}

@BindingAdapter("android:text")
public static void setText(TextView view, int value) {
    if (view.getText() != null
            && ( !view.getText().toString().isEmpty() )
            && Integer.parseInt(view.getText().toString()) != value) {
        view.setText(Integer.toString(value));
    }
}
  • If you try to start writing negative number it will crash (you can't convert "-" to integer). – Kamil Oct 05 '19 at 21:27
3

Here is my solution. It's clean and simple. Simply if layout needs String, give it a String instead of int. All you have to do is create a setter and getter with String type and use them to bind to ui while normal setter and getter doing the normal thing!

A complete code !

My POJO class(Mydata.java). getAgeString and setAgeString are the ui methods, doing the conversion. Note that I put @Bindable on getAgeString. so ui will use ageString

package com.databindingnumber;

import android.databinding.BaseObservable;
import android.databinding.Bindable;

public class MyData extends BaseObservable{
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if(this.age != age) {
            this.age = age;
            notifyPropertyChanged(BR.ageString);//NOTE: ui is using ageString !
        }
    }

    @Bindable
    public String getAgeString() {
        return Integer.toString(age);
    }

    public void setAgeString(String ageString) {
        try {
            int val = Integer.parseInt(ageString);
            this.setAge(val);
        }catch(NumberFormatException ex){
            this.setAge(0);//default value
        }
    }
}

The Layout File(activity_main.xml). use normal two-way binding with @= but use ageString instead of age

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="foo" type="com.databindingnumber.MyData"/>
    </data>

    <EditText
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:inputType="numberSigned"
        android:text="@={foo.ageString}" />
</layout>

MainActivity.java file

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setFoo(new MyData());
    }
}

Hope this will help to someone!

mili
  • 3,502
  • 1
  • 29
  • 29
3

The way of using @xdbas's solution

DataBindingConverter.kt

class DataBindingConverters {
    companion object {

        @InverseMethod("convertIntegerToString")
        @JvmStatic
        fun convertStringToInteger(value: String): Int? {
            if (TextUtils.isEmpty(value) || !TextUtils.isDigitsOnly(value)) {
                return null
            }
            return value.toIntOrNull()
        }

        @JvmStatic
        fun convertIntegerToString(value: Int?): String {
            return value?.toString() ?: ""
        }
    }
}

XML import

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <import type="com.package.DataBindingConverter" />
    </data>
.....

Bind to textView

<EditText
    ...
    android:text="@={DataBindingConverter.convertStringToInteger(ViewModel.user.age)}" />

Maybe I should have edited his answer but i don't know if it didn't work for him.

nyxee
  • 2,773
  • 26
  • 22
2

This might help some people who need to get this to work with two way databinding and kotlin.

DataBindingConverter.kt

class DataBindingConverter {
    companion object {

        @InverseMethod("convertStringToInteger")
        @JvmStatic
        fun convertIntegerToString(value: String): Int? {
            if (TextUtils.isEmpty(value) || !TextUtils.isDigitsOnly(value)) {
                return null
            }

            return value.toIntOrNull()
        }

        @JvmStatic
        fun convertStringToInteger(value: Int?): String {
            return value?.toString() ?: ""
        }
    }
}

import that class in your view

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <import type="com.package.DataBindingConverter" />
    </data>
.....

bind it to a textview

<EditText
    ...
    android:text="@={DataBindingConverter.convertStringToInteger(ViewModel.user.age)}" />
xdbas
  • 663
  • 6
  • 19
0

Add following in strings.xml:

<resources>
    <string name="_int">%d</string>
</resources>

Then you can do:

<EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{@string/_int(user.age)}" />