61

In Yigit Boyar and George Mount's talk on Android Databinding they illustrate how easy it is to bind to TextWatcher's onTextChanged (at 13:41). On a Button. Are their slides wrong? First of all the Button View doesn't have an onTextChanged property. It neither has a setOnTextChanged method. Neither does EditText. But they both have addTextChangedListener which takes a TextWatcher as input.

So what are they talking about? How do they do it? Their example code does not compile, but gives this error:

Error:(17) No resource identifier found for attribute 'onTextChanged' in package 'android'

How do I bind to a "Text Changed Event" on any View, or EditText in particular, with the Android Databinding framework?

stkent
  • 19,772
  • 14
  • 85
  • 111
Nilzor
  • 18,082
  • 22
  • 100
  • 167
  • The Yigit video link is showing only images of them talking. You can't tell what is happening. – Mitch Aug 27 '19 at 07:26

10 Answers10

109

Actually it works out of the box. I think my mistake was using an old version of the data binding framework. Using the latest, this is the procedure:

View:

<EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/username"
    android:text="Enter username:"
    android:onTextChanged="@{data.onTextChanged}" />

Model:

public void onTextChanged(CharSequence s, int start, int before, int count) {
    Log.w("tag", "onTextChanged " + s);
}

Make also sure that you have assigned model into DataBinding

For ex. in your activity

lateinit var activityMainDataBinding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    activityMainDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) 
    val dataViewModel = ViewModelProvider(this).get(DataViewModel::class.java)
    activityMainDataBinding.dataModel = dataViewModel
}

Make sure you are referncing gradle build tools v1.5.0 or higher and have enabled databinding with android.dataBinding.enabled true in your build.gradle.

edit: Functioning demo project here. view. model.

Siddhpura Amit
  • 14,534
  • 16
  • 91
  • 150
Nilzor
  • 18,082
  • 22
  • 100
  • 167
  • 19
    This solution does not work. EditText does not have an onTextChanged attribute. – M. Palsich Jun 07 '17 at 21:57
  • 4
    @M.Palsich it just works. https://news.realm.io/news/data-binding-android-boyar-mount/ – Sotti Jul 17 '17 at 10:16
  • 2
    Yeah EditText doesn't need an onTextChanged attribute because it is defined as a BindingAdapter: https://android.googlesource.com/platform/frameworks/data-binding/+/android-6.0.0_r7/extensions/baseAdapters/src/main/java/android/databinding/adapters/TextViewBindingAdapter.java#299 . tl;dr: Data binding is magic. – Nilzor Jul 31 '17 at 17:43
  • 1
    is there a way to act on the edittext in the onTextChanged method? for example let's say that I want to change the text color. – sliders_alpha Aug 10 '17 at 12:56
  • Sure, just bind `android:textColor` to an `ObservableInt` and update that value from `onTextChanged` in the ViewModel – Nilzor Aug 12 '17 at 12:34
  • 3
    This does not work, just tried it. Don't know how it was accepted or how any of you got it to work – clementiano Sep 04 '17 at 12:53
  • @clementiano: Added link to functioning github project in the answer. Most likely you have the wrong signature on your method. – Nilzor Sep 10 '17 at 11:44
  • 1
    So I'm new to data binding in Android Studio and I follow your example in project and it didn't work. After searching and searching and searching, I found out that I should set the viewmodel to the variable like this `binding.setData(myViewModel);`. It is `setData` because I named the viewmodel's variable as `data` in my xml, like this ``. Then it worked. Anyway thank you very much man, this really helps. – Konayuki Sep 09 '18 at 03:43
  • @Nilzor your this line helps me Make also sure that you have assigned model into DataBinding :D – Ankur Chaudhary Oct 15 '20 at 11:34
  • The best solution is the bellow from @Khemraj Sharma. That is what written in Android Developer site – zihadrizkyef Nov 24 '21 at 06:07
64

To extend @Nilzors answer, it is also possible to pass text and/or other parameters in the layout:

View:

<EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/username"
    android:text="Enter username:"
    android:onTextChanged="@{(text, start, before, count) -> viewModel.onUsernameTextChanged(text)}" />

ViewModel:

public void onUsernameTextChanged(CharSequence text) {
    // TODO do something with text
}

You always need to pass either zero or all parameters.

Micer
  • 8,731
  • 3
  • 79
  • 73
  • 4
    Does not work! It says onTextChanged is not an attribute – AnupamChugh Aug 04 '18 at 14:34
  • 8
    If you've setup databinding properly in your project, then you have some binding adapters already prepared and this is one of them. See https://android.googlesource.com/platform/frameworks/data-binding/+/android-6.0.0_r7/extensions/baseAdapters/src/main/java/android/databinding/adapters/TextViewBindingAdapter.java#299 and see other comments bellow @Nilzor's answer. – Micer Aug 06 '18 at 14:14
  • 3
    how to write this for afterTextChange method ? I don't want to pass any data to method, I'll fetch text from LiveData itself. – akshay bhange Apr 26 '19 at 06:54
43

The Easy Way

If you are using onTextChange() to update text in the model then you can use Two-way Binding directly.

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

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

The model class will have a getter and setter:

public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Now the name in the model will be changed in realtime with user interaction. So using binding.getUser().getName() will get the latest text.

One-Way Binding will only gets updated when model value is changed. It does not update model back real time.

android:text="@{user.name}"

Two-Way Binding update model variable in real time with user input.

android:text="@={user.name}"

The ONLY difference is of = (equal sign) receives data changes to the property and listen to user updates at the same time.

TT--
  • 2,956
  • 1
  • 27
  • 46
Khemraj Sharma
  • 57,232
  • 27
  • 203
  • 212
11

If you just need text parameter after text has changed, you could use android:afterTextChanged binding adapter. for example:

android:afterTextChanged="@{(text) -> viewModel.onTextChange(text)}"

Then in your ViewModel just implement like this:

fun onTextChange(editable: Editable?) {
    Log.d("TAG","New text: ${editable.toString()}")
}

Furthermore, there is android:beforeTextChanged which used to know old text before text change event, usage is same as android:afterTextChanged.

sma6871
  • 3,198
  • 3
  • 38
  • 52
6

Im using this method to handle on text change listener in android databinding.1st in your ViewModel class create LiveData variable, And also create getText method which returns LiveData object.

  • public MutableLiveData<String> verifyCodes = new MutableLiveData<>();
  • public LiveData<String> getCodes(){ return verifyCodes; }

Then in your xml files editText field set attribute on text bind with above liveData field

  • <EditText android:id="@+id/et_verification1st" android:layout_width="match_parent" android:layout_height="match_parent" android:text="@={viewModel.verifyCodes}"/>

In databinding you already know how to create variable of viewModel class inside data tag i beleive.As example

  • <data> <variable name="viewModel" type="viewModel.VerifyUserActivityViewModel" /> </data>

Ok now your activity you have to observe liveData object we created inside viewModel class

  • mViewModel.getCodes().observe(this,new Observer< String>(){ @Override public void onChange(String strings){ log.d("OnChange",strings); }});

You can perform any logic when text changing inside onChange method

Ashana.Jackol
  • 3,064
  • 28
  • 22
5

1. In your BindingAdapter class, write down this. Here I have passed viewModel so that we can do particular task against particular viewModel:

@BindingAdapter("app:addTextChangeListener")
fun addTextChangeListener(view: EditText, viewModel: ViewModel) {

    view.addTextChangedListener(object : TextWatcher {
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        }

        override fun afterTextChanged(s: Editable?) {
            when (viewModel) {
                is LoginViewModel -> viewModel.invisibleErrorTexts()
            }
        }

    })

}

2. In your XML, in the Edittext, put the attribute given below: Here "viewModel" is the variable name of the LoginViewModel in my layout tag

app:addTextChangeListener="@{viewModel}"
Joy Rajak
  • 51
  • 1
  • 2
3

I got it working like this:

Fragment:

    class DiAtomicMoleculesFragment : Fragment() {
        private lateinit var binding: FragmentDiatomicMoleculesBinding
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            binding = FragmentDiatomicMoleculesBinding.inflate(layoutInflater, container, false)
            return binding.root
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            val recyclerAdapter = DiAtomicMoleculesAdapter(onClickListener)
    
            binding.diRecyclerView.apply {
                setHasFixedSize(true)
                layoutManager = LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
                adapter = recyclerAdapter
            }
    
            val viewModel = ViewModelProvider(this).get(DiAtomicMoleculesViewModel::class.java)
            viewModel.getDiAtomicMoleculesByName().observe(viewLifecycleOwner, Observer { items ->
                recyclerAdapter.setData(items)
            })
    
            //this is important !!!!!!!!!!!!!
            binding.viewModel = viewModel
        }
    }

layout.xml:

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

    <data>
        <variable
            name="viewModel"
            type="com.mychemistry.viewmodel.DiAtomicMoleculesViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white">

        <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/di_search_box"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:gravity="center_vertical"
            android:hint="@string/search"
            android:paddingStart="10dp"
            android:paddingEnd="5dp"
            android:singleLine="true"
            android:textColor="@android:color/black"
            android:textColorHint="@android:color/darker_gray"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:onTextChanged="@{(text, start, before, count) -> viewModel.onTextChange(text)}"/>

<!--     or this ->      android:afterTextChanged="@{(e) -> viewModel.onTextChange(e)}"/>-->

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/di_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/di_search_box" />

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

ViewModel:

class DiAtomicMoleculesViewModel : ViewModel() {
    private val allLiveData = AppDatabase.getInstance().getDiAtomicMoleculeDao().getAll()
    private val filterLiveData = MutableLiveData<String>()
    private val searchByLiveData = Transformations.switchMap(filterLiveData, ::filter)

    fun getDiAtomicMolecules(): LiveData<List<DiAtomicMolecule>> {
        return allLiveData
    }

    private fun filter(text: String): LiveData<List<DiAtomicMolecule>> {
        return AppDatabase.getInstance().getDiAtomicMoleculeDao().find(text)
    }

    fun getDiAtomicMoleculesByName(): LiveData<List<DiAtomicMolecule>> {
        return searchByLiveData
    }

    fun onTextChange(e: Editable?) {
        filterLiveData.value = e?.toString()
    }

    fun onTextChange(text: CharSequence?) {
        filterLiveData.value = text?.toString()
    }
}
Goran Horia Mihail
  • 3,536
  • 2
  • 29
  • 40
1
  1. create a class (I named him BindingAdapters). Then define your bindingAdapter methods.

    @BindingAdapter("app:textChangedListener")
    fun onTextChanged(et: EditText, number: Int) {
    et.addTextChangedListener(object : TextWatcher {
        override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
            if (et.text.toString().trim().length >= number) {
                et.setBackgroundColor(Color.GREEN)
            }
        }
    
        override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
        override fun afterTextChanged(s: Editable) {}
    })
    

    }

  2. set this attr for editText in xml layout

    <EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:textChangedListener="@{3}" />  
    
roghayeh hosseini
  • 676
  • 1
  • 8
  • 21
0

the best way for this is adding bind adapter and a text watcher.

public class Model{
    private TextWatcher textWatcher;

public Model(){
        this.textWatcher= getTextWatcherIns();
}

private TextWatcher getTextWatcherIns() {
        return new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                //do some thing
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                //do some thing

            }

            @Override
            public void afterTextChanged(Editable s) {
                //do some thing
            }
        };
    }

    public TextWatcher getTextWatcher() {
        return textWatcher;
    }

    public void setTextWatcher(TextWatcher textWatcher) {
        this.textWatcher = textWatcher;
    }

    @BindingAdapter("textChangedListener")
    public static void bindTextWatcher(EditText editText, TextWatcher textWatcher) {
        editText.addTextChangedListener(textWatcher);
    }
}

and in your xml add this attr to your edit text

<EditText
            android:id="@+id/et"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:textChangedListener="@{model.textWatcher}" />
mahdi shahbazi
  • 1,882
  • 10
  • 19
  • ava.lang.IllegalStateException: Required DataBindingComponent is null in class SpotFragmentBindingImpl. A BindingAdapter in com.mytrain.findmytrain.ui.viewmodel.SpotViewmodel is not static and requires an object to use, retrieved from the DataBindingComponent. If you don't use an inflation method taking a DataBindingComponent, use DataBindingUtil.setDefaultComponent or make all BindingAdapter methods static. – ABDUL RAHMAN Nov 21 '19 at 11:47
  • @ABDULRAHMAN your BindingAdapter method is not static.Am I right? – mahdi shahbazi Nov 23 '19 at 13:04
-7

Attach an setOnFocusChangeListener to the EditText, and use compare the textual content with a global variable (of the previous state/content of the field) to determine whether it has changed:

    mEditTextTitle.setOnFocusChangeListener(new View.OnFocusChangeListener() {
        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if(!hasFocus)
                // check if text has changed       
        }
    });
joakimk
  • 822
  • 9
  • 26
  • At least, that's a way to *achieve* "onTextChanged", but that's not exactly what you're asking. My solution only fires when the user actually *leaves* the EditText. – joakimk Nov 20 '15 at 21:28