Edittext cursor resetting to left after Android Data Binding update-ThrowExceptions

Exception or error:

I am trying out the new Android Data Binding library (1.0-rc1) and I have made a User object with three String fields (name, email, and age) and linked them to 3 EditTexts in my layout.

On the first field (name) I placed a TextWatcher. Everything seems to work well. I prevented the notifyPropertyChanged loop in the name field by checking to see if the text is different before allowing it to call setName.

The problem is, every time I type in the name field, the cursor resets to the left of the EditText after each character. I googled around for a solution but most fix suggestions for a cursor issue say to grab a reference to the EditText and adjust the cursor position manually. But I’d like to avoid doing that since I then need to findViewByID to the EditText and the point of Data Binding was to try to avoid doing that. Thank you for your help.

My layout looks like this:

<layout>

<data>
    <variable name="user" type="com.carlpoole.databindingstest.User"/>
</data>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <EditText
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:id="@+id/name"
        android:text="@{user.name}"
        bind:addTextChangedListener="@{user.nameChanged}"
        />

    <EditText
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:id="@+id/email"
        android:layout_below="@+id/name"
        android:text="@{user.email}"/>

    <EditText
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:id="@+id/age"
        android:layout_below="@+id/email"
        android:text="@{user.age}"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/age"
        android:text="@{user.name}"/>

</RelativeLayout>

My user object looks like this:

import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.text.Editable;
import android.text.TextWatcher;

public class User extends BaseObservable {

    private String name;
    private String email;
    private String age;

    public User(String name, String email, String age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }

    public User(){};

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

    @Bindable
    public String getEmail() {
        return email;
    }

    @Bindable
    public String getAge() {
        return age;
    }

    public final TextWatcher nameChanged = new TextWatcher() {
        @Override
        public void afterTextChanged(Editable s) {
            if(!s.toString().equalsIgnoreCase(name))
                setName(s.toString());
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {}
    };

    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(com.carlpoole.databindingstest.BR.name);
    }

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

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

My activity looks like this

import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import com.carlpoole.databindingstest.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

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

        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        User user = new User("Carl Poole", "mail@carlpoole.com", "26");
        binding.setUser(user);

    }
}
How to solve:

Your best bet here is to use a custom @BindingAdapter which will already have a reference to the EditText. That way you can avoid re-binding if the text in the EditText matches your model, which will resolve your cursor issue.

First, change android:text="@{user.name}" to bind:binding="@{user.name}". Then, add this static method anywhere in your project. We keep all of them in a class called BindingAdapters.java. By the way, starting in RC2 you can create non-static binding adapter methods, but that probably isn’t your biggest concern right now.

@BindingAdapter("binding")
public static void bindEditText(EditText editText, CharSequence value) {
  if (!editText.getText().toString().equals(value.toString())) {
    editText.setText(value);
  }
}

###

To fix the weird data binding behaviour that resets the cursor to the start of the EditText, you can add the following InverseBindingAdapter :

  @SuppressLint("RestrictedApi")
  @BindingAdapter("android:text")
  public static void setText(EditText view, String oldText, String text) {

    TextViewBindingAdapter.setText(view, text);
    if (text == null) return;
    if (text.equals(oldText) || oldText == null) {
      view.setSelection(text.length());
    }
  }

###

The problem is with setter confusion: your setter, as recommended by the DataBinding documentation, calls the notifyPropertyChanged method. But the notifyPropertyChanged method, among other things, resets the cursor which is whats causing your problem. Their is no need for the setter to update the UI when it was the UI (TextWatcher) that is calling the setter. The solution then is to have setters only call the notifyPropertyChanged method when some backend calculation/manipulation should cause the UI to be updated.

###

Try this:

@BindingAdapter("android:text")
fun setStringWIthSelection(view: EditText, str : String) {
    view.setText(str)
    view.setSelection(view.text.length)
}

###

We can do this without any new BindingAdapter. The below is my EditText in XML. And viewModel is my DataBinding variable. And I will set the cursor in android:onTextChanged in XML itself

<androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/et_enter_pincode"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="number"
            android:maxLength="6"
            android:text="@={viewModel.observableEditText}"
            android:textSize="16sp"
            android:onTextChanged="@{(text, start, before, count) -> viewModel.onPincodeTextChanged(etEnterPincode)}"
            android:hint="@string/enter_pincode"/>

Inside the View Model the code looks like this

val observableEditText = ObservableField<String>("")

fun onPincodeTextChanged(
        view: EditText
){
    view.setSelection(view.text.length)
}

Leave a Reply

Your email address will not be published. Required fields are marked *