How to databind to onTextChanged for an EditText on Android?

AndroidAndroid Databinding

Android Problem Overview


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?

Android Solutions


Solution 1 - Android

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.

Solution 2 - Android

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.

Solution 3 - Android

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.

Solution 4 - Android

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.

Solution 5 - Android

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

Solution 6 - Android

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

Solution 7 - Android

  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}" />  
    

Solution 8 - Android

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()
    }
}

Solution 9 - Android

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}" />

Solution 10 - Android

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       
        }
    });

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNilzorView Question on Stackoverflow
Solution 1 - AndroidNilzorView Answer on Stackoverflow
Solution 2 - AndroidMicerView Answer on Stackoverflow
Solution 3 - AndroidKhemraj SharmaView Answer on Stackoverflow
Solution 4 - Androidsma6871View Answer on Stackoverflow
Solution 5 - AndroidAshana.JackolView Answer on Stackoverflow
Solution 6 - AndroidJoy RajakView Answer on Stackoverflow
Solution 7 - Androidroghayeh hosseiniView Answer on Stackoverflow
Solution 8 - AndroidGoran Horia MihailView Answer on Stackoverflow
Solution 9 - Androidmahdi shahbaziView Answer on Stackoverflow
Solution 10 - AndroidjoakimkView Answer on Stackoverflow