How to databind to onTextChanged for an EditText on Android?
Asked Answered
R

10

63

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?

Regen answered 19/11, 2015 at 8:15 Comment(1)
The Yigit video link is showing only images of them talking. You can't tell what is happening.Proteolysis
R
110

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.

Regen answered 21/11, 2015 at 9:30 Comment(10)
This solution does not work. EditText does not have an onTextChanged attribute.Carcass
@M.Palsich it just works. news.realm.io/news/data-binding-android-boyar-mountGelasius
Yeah EditText doesn't need an onTextChanged attribute because it is defined as a BindingAdapter: android.googlesource.com/platform/frameworks/data-binding/+/… . tl;dr: Data binding is magic.Regen
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.Vercelli
Sure, just bind android:textColor to an ObservableInt and update that value from onTextChanged in the ViewModelRegen
This does not work, just tried it. Don't know how it was accepted or how any of you got it to workDecompound
@clementiano: Added link to functioning github project in the answer. Most likely you have the wrong signature on your method.Regen
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 <variable name="data" type=".viewmodel.MyViewModel"/>. Then it worked. Anyway thank you very much man, this really helps.Impressment
@Regen your this line helps me Make also sure that you have assigned model into DataBinding :DHallock
The best solution is the bellow from @Khemraj Sharma. That is what written in Android Developer siteGnathonic
P
66

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.

Portauprince answered 17/8, 2017 at 10:23 Comment(3)
Does not work! It says onTextChanged is not an attributeAspirant
If you've setup databinding properly in your project, then you have some binding adapters already prepared and this is one of them. See android.googlesource.com/platform/frameworks/data-binding/+/… and see other comments bellow @Nilzor's answer.Portauprince
how to write this for afterTextChange method ? I don't want to pass any data to method, I'll fetch text from LiveData itself.Parlormaid
B
44

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.

Broglie answered 9/8, 2018 at 9:6 Comment(8)
yes. This is the easiest solution !! working as expected! I hope people scroll down and look at this answer !!Enchanting
This won't work, unless you are okay with extraneous callbacks happening.Proteolysis
How can I print name when it's changed?Gabbro
it worked with me .. Thanks @Khemraj , you might need to use livedata val name = MutableLiveData("") then name.observeForever { /*you code here to listen to changes*/ }Annadiane
As i expect, That isArmed
@Khemraj, but how can I get immediate data when I start to write in edittextSheared
@SiddhpuraAmit Your data will be captured in user.name as showed in above example.Broglie
This is the official solution. this should be accepted answerGnathonic
C
12

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.

Cappadocia answered 19/1, 2020 at 13:29 Comment(0)
I
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

Insider answered 27/4, 2020 at 6:13 Comment(1)
Doesn't your approach come at the expense of exposing a MutableLiveData to the Acitivity/Fragment?Biotechnology
V
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}"
Valorize answered 9/8, 2021 at 8:37 Comment(0)
R
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()
    }
}
Repression answered 8/2, 2020 at 15:36 Comment(0)
H
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}" />  
    
Hume answered 6/10, 2019 at 6:52 Comment(0)
D
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}" />
Decimal answered 16/4, 2019 at 5:25 Comment(2)
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.Harbin
@ABDULRAHMAN your BindingAdapter method is not static.Am I right?Decimal
D
-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       
        }
    });
Dextrose answered 20/11, 2015 at 21:21 Comment(1)
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.Dextrose

© 2022 - 2024 — McMap. All rights reserved.