Kotlin addTextChangeListener lambda?
Asked Answered
C

13

171

How do you build a lambda expression for the EditText addTextChangeListener in Kotlin? Below gives an error:

passwordEditText.addTextChangedListener { charSequence  ->
    try {
        password = charSequence.toString()
    } catch (error: Throwable) {
        raise(error)
    }
}
Coopt answered 13/11, 2016 at 1:0 Comment(2)
What error does it gives?Selfpity
Hi think my question is edited to suit his answer.Coopt
C
-11

This looks neat:

passwordEditText.setOnEditorActionListener { 
    textView, keyCode, keyEvent ->
    val DONE = 6

    if (keyCode == DONE) {                       
         // your code here
    }
    false
}
Coopt answered 13/11, 2016 at 2:16 Comment(2)
I really don't know with you guys but my answer worked for me and is shorter than the answer above and below..Coopt
This works as what the code intends to be, performs action once DONE is pressed. I modified the code by placing a toast outside the condition and it seems to only fires when pressing TAB/DONE/etc. but not on other characters.Kyoko
U
362

addTextChangedListener() takes a TextWatcher which is an interface with 3 methods. What you wrote would only work if TextWatcher had only 1 method. I'm going to guess the error you're getting relates to your lambda not implementing the other 2 methods. You have 2 options going forward.

  1. Ditch the lambda and just use an anonymous inner class
    editText.addTextChangedListener(object : TextWatcher {
      override fun afterTextChanged(s: Editable?) {
      }
    
      override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
      }
    
      override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
      }
    })
  1. Create an extension method so you can use a lambda expression:
    fun EditText.afterTextChanged(afterTextChanged: (String) -> Unit) {
        this.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(editable: Editable?) {
            afterTextChanged.invoke(editable.toString())
          }
        })
    }

And then use the extension like so:

editText.afterTextChanged { doSomethingWithText(it) }
Understandable answered 13/11, 2016 at 1:57 Comment(5)
Not sure if personal preference or better style, but your extension function could be converted to an expression body (fun foo() = ...)Caitiff
@mEQ5aNLrK3lqs3kfSa5HbvsTWe0nIu You're right that it can be converted. However, for functions longer than one line, I like having surrounding brackets to clearly mark where the function starts and stops. I believe it increases readability, but it's totally a style preference. I think it could be argued both ways :)Understandable
Out of interest: Why call afterTextChanged.invoke(...) instead of afterTextChanged(...)?Compassionate
This worked for me. I preferred the 2nd option for reusability.Kyoko
But if that wouldn't be possible, why does the IDE allow the addTextChangedListener{} at all? The code for that however looks like this: public inline fun TextView.addTextChangedListener( crossinline beforeTextChanged: ( text: CharSequence?, start: Int, count: Int, after: Int ) -> Unit = { _, _, _, _ -> }, [...] crossinline afterTextChanged: (text: Editable?) -> Unit = {} ): TextWatcherMantooth
B
100

Add this core ktx dependence

implementation 'androidx.core:core-ktx:1.0.0'

You simply have to do

passwordEditText.doAfterTextChanged{ }

Butyraceous answered 3/10, 2019 at 10:16 Comment(0)
H
34

A bit old, but using Kotlin Android extensions you can do something like that:

editTextRequest.textChangedListener {
            afterTextChanged {
                // Do something here...
            }
}

No extra code needed, just add:

implementation 'androidx.core:core-ktx:1.0.0'
Handfast answered 24/5, 2018 at 8:42 Comment(5)
That's not working for me, even after refactoring to android X. Any guess to what I could be doing wrong?Pettitoes
Does not work for me as well. It looks like KTX does not provide this extension anymore, however, KAndroid solution works perfectly.Subscription
with ktx:1.0.1 you can use developer.android.com/reference/kotlin/androidx/core/widget/…Radiotransparent
implementation "androidx.core:core-ktx:1.2.0" -> edt.addTextChangedListener(afterTextChanged = { /*dosomething*/ })Warbeck
It's easier to just use editTextRequest.doAfterTextChanged { ... }Umont
P
21

Sorry for being late!

If you add implementation 'androidx.core:core-ktx:1.1.0' to your module's build.gradle file then you can use

etPlayer1.doOnTextChanged { text, start, count, after -> // Do stuff }
Pulp answered 19/2, 2020 at 9:16 Comment(0)
I
19

Test it :

passwordEditText.addTextChangedListener(object:TextWatcher{
    override fun afterTextChanged(s: Editable?) { }

    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { }
})
Intractable answered 22/5, 2019 at 13:39 Comment(0)
V
15

hope this Kotlin sample help making it clear:

class MainFragment : Fragment() {

    private lateinit var viewModel: MainViewModel

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View {
    val view = inflater.inflate(R.layout.main_fragment, container, false)

    view.user.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) {
                userLayout.error =
                        if (s.length > userLayout.counterMaxLength) {
                            "Max character length is: ${userLayout.counterMaxLength}"
                        } else null
        }
    })
    return view
}

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
    // TODO: Use the ViewModel
   }
}

With this XML layout:

<android.support.design.widget.TextInputLayout
    android:id="@+id/userLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:counterMaxLength="5"
    app:counterEnabled="true"
    android:hint="user_name">

    <android.support.design.widget.TextInputEditText
        android:id="@+id/user"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</android.support.design.widget.TextInputLayout>

And this Gradle:

android {
    compileSdkVersion 'android-P'
...
}
    api 'com.android.support:design:28.0.0-alpha1'

    implementation 'com.android.support:appcompat-v7:28.0.0-alpha1' // appcompat library
Verbiage answered 1/6, 2018 at 22:18 Comment(0)
P
15

In case you're using Material Filled text field or Outlined text field, attempt to respond to input text change as mentioned by documentation, respectively:

filledTextField.editText?.doOnTextChanged { inputText, _, _, _ ->
    // Respond to input text change
}

and

outlinedTextField.editText?.doOnTextChanged { inputText, _, _, _ ->
    // Respond to input text change
}
Plagioclase answered 21/2, 2021 at 17:29 Comment(0)
R
12

if you use implementation 'androidx.core:core-ktx:1.1.0-alpha05' you can use

For android.widget.TextView
TextWatcher 
TextView.doBeforeTextChanged(crossinline action: (text: CharSequence?, start: Int, count: Int, after: Int) -> Unit)
Add an action which will be invoked before the text changed.

TextWatcher 
TextView.doOnTextChanged(crossinline action: (text: CharSequence?, start: Int, count: Int, after: Int) -> Unit)
Add an action which will be invoked when the text is changing.

TextWatcher 
TextView.doAfterTextChanged(crossinline action: (text: Editable?) -> Unit)

https://developer.android.com/reference/kotlin/androidx/core/widget/package-summary#extension-functions

Radiotransparent answered 4/4, 2019 at 19:57 Comment(0)
C
11

Add the core ktx dependency

implementation 'androidx.core:core-ktx:1.3.0'

And you can simply implement like this

    edit_text.addTextChangedListener { it: Editable? ->
      // Do your stuff here
    }
Craig answered 8/6, 2020 at 4:34 Comment(0)
N
3

Another alternative is the KAndroid library -

implementation 'com.pawegio.kandroid:kandroid:0.8.7@aar'

Then you could do something like this...

editText.textWatcher { afterTextChanged { doSomething() } }

Obviously it is excessive to use an entire library to solve your problem, but it also comes with a range of other useful extensions that eliminate boilerplate code in the Android SDK.

Nolanolan answered 24/8, 2017 at 8:5 Comment(0)
M
3

You can make use of kotlin's named parameters:

private val beforeTextChangedStub: (CharSequence, Int, Int, Int) -> Unit = { _, _, _, _ -> }
private val onTextChangedStub: (CharSequence, Int, Int, Int) -> Unit = { _, _, _, _ -> }
private val afterTextChangedStub: (Editable) -> Unit = {}

fun EditText.addChangedListener(
        beforeTextChanged: (CharSequence, Int, Int, Int) -> Unit = beforeTextChangedStub,
        onTextChanged: (CharSequence, Int, Int, Int) -> Unit = onTextChangedStub,
        afterTextChanged: (Editable) -> Unit = afterTextChangedStub
) = addTextChangedListener(object : TextWatcher {
    override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
        beforeTextChanged(charSequence, i, i1, i2)
    }

    override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
        onTextChanged(charSequence, i, i1, i2)
    }

    override fun afterTextChanged(editable: Editable) {
        afterTextChanged(editable)
    }
})
Mouflon answered 5/4, 2019 at 12:6 Comment(0)
R
3

This is the lambda function with edit text field with TextWatcher

searchField.addTextChangedListener(
        afterTextChanged = {

        },
        onTextChanged = {s, start, before, count->
            TODO("DO your code")
        },
        beforeTextChanged = {s, start, before, count->
           TODO("DO your code")
        }
    )
Renfro answered 18/3, 2022 at 12:19 Comment(0)
C
-11

This looks neat:

passwordEditText.setOnEditorActionListener { 
    textView, keyCode, keyEvent ->
    val DONE = 6

    if (keyCode == DONE) {                       
         // your code here
    }
    false
}
Coopt answered 13/11, 2016 at 2:16 Comment(2)
I really don't know with you guys but my answer worked for me and is shorter than the answer above and below..Coopt
This works as what the code intends to be, performs action once DONE is pressed. I modified the code by placing a toast outside the condition and it seems to only fires when pressing TAB/DONE/etc. but not on other characters.Kyoko

© 2022 - 2024 — McMap. All rights reserved.