2

I was trying to view-model in android, so for MainViewModel.java I wrote this :


public class MainViewModel extends ViewModel {

    private String textView;
    private String editText;

    //@Bindable
    public String getTextView(){
        return textView;
    }

    private void setTextView(String value){
        textView=value;
    }

    //@Bindable
    public TextWatcher getEditTextWatcher() {
        return new TextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                setTextView(s.toString());
            }
            ...
        };
    }

}

And in the ActivityMain.xml I wrote this :


        <TextView
            android:text="View-Model / Data-Binding"
            android:layout_width="match_parent"
            android:layout_height="40dp"/>

        <TextView
            android:id="@+id/main_text_view"
            android:text="@{mainvm.textView}"
            android:layout_width="match_parent"
            android:layout_height="40dp"/>

        <EditText
            android:id="@+id/main_edit_text"
            app:textChangeListener="@{mainvm.editTextWatcher}"
            android:layout_width="match_parent"
            android:layout_height="40dp"/>

I'm getting 2 errors:

Cannot find a setter for <android.widget.EditText app:textChangeListener> that accepts parameter type 'android.text.TextWatcher'

If a binding adapter provides the setter, check that the adapter is annotated correctly and that the parameter type matches.

And,

error: cannot find symbol class ActivityMainBindingImpl

Some article uses @Binable annotation extending BaseObservable, which is not a ViewModel thing.

So my question how can I solve this ?

Danial
  • 542
  • 2
  • 9
  • 24

3 Answers3

6

You cannot extend both BaseObservable and ViewModel in the same class. You can use ObservableFields inside of the ViewModel.

You need to use Two-way databinding by adding a class that extends from BaseObservable

Within this class, create a field for the text you need to observe; then annotate its getter with @Bindable and call notifyPropertyChanged() within its setter

ViewModel

public class MainViewModel extends ViewModel {

    Observer mObserver = new Observer();

    Observer getObserver() {
        return mObserver;
    }

    public static class Observer extends BaseObservable {
        private String text;

        @Bindable
        public String getText() {
            return text;
        }

        public void setText(String value) {
            text = value;
            notifyPropertyChanged(BR.text);
        }
    }

}

Activity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding mActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        MainViewModel mViewModel = new ViewModelProvider(this).get(MainViewModel.class);
        mActivityMainBinding.setObserver(mViewModel.getObserver());
    }
}

Layout

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

        <variable
            name="observer"
            type="com.zain.android.twowaydatabindingedittextobservable.MainViewModel.Observer" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/tvName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{observer.text}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <EditText
            android:id="@+id/etName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={observer.text}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tvName" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Wish that can help you

Zain
  • 37,492
  • 7
  • 60
  • 84
  • Is there a reason why the `Observer` class within the `MainViewModel` is static? And do you know a elegant solution to provide parameters to an `Observer` like a database connection or and item Id? – Bruno Bieri Apr 23 '21 at 14:42
  • @BrunoBieri It's according your need, it can be an inner class, but here it's a static-inner class that can only be accessed using the outer class name, so here I restricted the instantiation of the inner class to be through the outer class... The static nested class cannot access non-static (instance) data member or method... You can make it non-static if you wish .. Please have a look [here](https://stackoverflow.com/questions/70324/java-inner-class-and-static-nested-class) – Zain Apr 23 '21 at 14:53
  • 1
    The documentation is not clear on this, thank you – Daniel Wilson Dec 10 '21 at 13:38
1

You need to create a BindingAdapter to properly use DataBinding. You can read more details here For example to create for EditText

@BindingAdapter("addEditTextWatcher")
fun bindEditText(editText: EditText, stringTextWatcher: StringTextWatcher) {
    val string = editText.text.toString()
    stringTextWatcher.setString(editText.text.toString())
    editText.addTextChangedListener(stringTextWatcher)
}

Then you need to create a TextWatcher instance in your ViewModel to bind it in .xml like this

        <EditText
            android:id="@+id/main_edit_text"
            app:textChangeListener="@{mainvm.editTextWatcher}"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            addIntEditTextWatcher="@{viewModel.yourTextWatcher}"
/>
Community
  • 1
  • 1
Stanislav Batura
  • 420
  • 4
  • 11
  • first of all, its in kotlin so i converted it in java and tried, and it gave me `...getBindEditText as an accessor or listener on the attribute.` and that old imp error as before – Danial Apr 07 '20 at 07:18
  • Strnage maybe your DataBinding initialization not correct. Add activity code and full layout.xml file. I will have a look – Stanislav Batura Apr 07 '20 at 08:09
0

I was making thing complicated for no reason. Here is my code :

public class MyViewModel extends ViewModel{
     private Observable<String> data=new Observable<>("");//initializing with empty string, so that it doesn't crash
     
     public String getData(){
         return data.get();//if I return the observable itself that will become mutable, outside the class right
     }
     public void setData(String data){
          data.setValue(data);//if I accept Observable as parameter that will change reference of data, that's why passing string
     }
}

In xml:

        <TextView
            android:id="@+id/main_text_view"
            android:text="@{myvm.data}"
            ...
        />

        <EditText
            android:id="@+id/main_edit_text"
            android:text=@{myvm.data}
            ....
        />

And bind this to Activity or Fragment as usual.

Danial
  • 542
  • 2
  • 9
  • 24