167

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)
    }
}
Andrew Orobator
  • 7,978
  • 3
  • 36
  • 36
LEMUEL ADANE
  • 8,336
  • 16
  • 58
  • 72

13 Answers13

356

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) }
ThomasW
  • 16,981
  • 4
  • 79
  • 106
Andrew Orobator
  • 7,978
  • 3
  • 36
  • 36
  • 4
    Not sure if personal preference or better style, but your extension function could be converted to an expression body (`fun foo() = ...`) – F. George Nov 13 '16 at 16:41
  • 7
    @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 :) – Andrew Orobator Nov 13 '16 at 18:16
  • 2
    Out of interest: Why call `afterTextChanged.invoke(...)` instead of `afterTextChanged(...)`? – Felix D. Jul 24 '19 at 14:13
  • This worked for me. I preferred the 2nd option for reusability. – Rod Maniego Nov 16 '19 at 12:59
  • 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 = {} ): TextWatcher – Tobias Reich Jun 08 '22 at 10:23
99

Add this core ktx dependence

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

You simply have to do

passwordEditText.doAfterTextChanged{ }

Alaa
  • 1,239
  • 10
  • 9
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'
Krasavello13
  • 313
  • 5
  • 9
Efi MK
  • 1,022
  • 1
  • 11
  • 26
  • 4
    That's not working for me, even after refactoring to android X. Any guess to what I could be doing wrong? – Nícolas Schirmer Oct 16 '18 at 01:50
  • 3
    Does not work for me as well. It looks like KTX does not provide this extension anymore, however, `KAndroid ` solution works perfectly. – Igor Nov 27 '18 at 13:58
  • 2
    with ktx:1.0.1 you can use https://developer.android.com/reference/kotlin/androidx/core/widget/package-summary#extension-functions – ingyesid Apr 04 '19 at 19:52
  • 2
    `implementation "androidx.core:core-ktx:1.2.0"` -> `edt.addTextChangedListener(afterTextChanged = { /*dosomething*/ })` – Dr.jacky Nov 04 '20 at 16:36
  • 4
    It's easier to just use editTextRequest.doAfterTextChanged { ... } – Monte Creasor Dec 14 '20 at 22:06
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 }
timmyBird
  • 223
  • 3
  • 7
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) { }
})
aeronaut
  • 53
  • 1
  • 7
Reza Khammary
  • 191
  • 1
  • 3
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
Hasan A Yousef
  • 22,789
  • 24
  • 132
  • 203
14

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
}
Filipe Brito
  • 5,329
  • 5
  • 32
  • 42
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

ingyesid
  • 2,864
  • 2
  • 23
  • 21
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
    }
Enzo Lizama
  • 1,214
  • 1
  • 14
  • 23
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.

aneurinc
  • 1,238
  • 12
  • 20
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)
    }
})
Dmitrii Bychkov
  • 829
  • 8
  • 3
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")
        }
    )
Kawsar
  • 111
  • 1
  • 4
-11

This looks neat:

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

    if (keyCode == DONE) {                       
         // your code here
    }
    false
}
LEMUEL ADANE
  • 8,336
  • 16
  • 58
  • 72
  • 1
    I really don't know with you guys but my answer worked for me and is shorter than the answer above and below.. – LEMUEL ADANE May 22 '19 at 00:57
  • 1
    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. – Rod Maniego Nov 16 '19 at 12:44