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", "[email protected]", "26");
binding.setUser(user);
}
}
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());
}
}
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);
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With