androidkotlinrx-binding

How to detect when an EditText is empty using RxTextView (RxBinding)


I'm doing validation on an EditText. I want the CharSequence to be invalid if it's empty or it doesn't begin with "https://". I'm also using RxBinding, specifically RxTextView. The problem is that when there is one character left, and I then delete it leaving no characters left in the the CharSequence the map operator doesn't fire off an emission. In other words I want my map operator to return false when the EditText is empty. I'm beginning to think this may not be possible the way I'm doing it. What would be an alternative?

Here is my Observable / Disposable:

val systemIdDisposable = RxTextView.textChanges(binding.etSystemId)
            .skipInitialValue()
            .map { charSeq ->
                if (charSeq.isEmpty()) {
                    false
                } else {
                    viewModel.isSystemIdValid(charSeq.toString())
                }
            }
            .subscribe { isValid ->
                if (!isValid) {
                    binding.systemIdTextInputLayout.isErrorEnabled = true
                    binding.systemIdTextInputLayout.error = viewModel.authErrorFields.value?.systemId
                } else {
                    binding.systemIdTextInputLayout.isErrorEnabled = false
                    binding.systemIdTextInputLayout.error = viewModel.authErrorFields.value?.systemId
                }
            }

And here is a function in my ViewModel that I pass the CharSequence to for validation:

fun isSystemIdValid(systemId: String?): Boolean {

    return if (systemId != null && systemId.isNotEmpty()) {
        _authErrors.value?.systemId = null 
        true
    } else {
        _authErrors.value?.systemId =
            getApplication<Application>().resources.getString(R.string.field_empty_error)
        false
    }
}

Solution

  • After sleeping on it, I figured it out.

    I changed RxTextView.textChanges to RxTextView.textChangeEvents. This allowed me to query the CharSequence's text value (using text() method provided by textChangeEvents) even if it's empty. Due to some other changes (not really relevant to what I was asking in this question) I was also able to reduce some of the conditional code too. I'm just putting that out there in case someone comes across this and is curious about these changes. The takeaway is that you can get that empty emission using RxTextView.textChangeEvents.

    Here is my new Observer:

     val systemIdDisposable = RxTextView.textChangeEvents(binding.etSystemId)
                .skipInitialValue()
                .map { charSeq -> viewModel.isSystemIdValid(charSeq.text().toString()) }
                .subscribe {
                    binding.systemIdTextInputLayout.error = viewModel.authErrors.value?.systemId
                }
    

    And here is my validation code from the ViewModel:

    fun isSystemIdValid(systemId: String?): Boolean {
    
            val auth = _authErrors.value
    
            return if (systemId != null && systemId.isNotEmpty()) {
                auth?.systemId = null
                _authErrors.value = auth
                true
            } else {
                auth?.systemId =
                    getApplication<Application>().resources.getString(R.string.field_empty_error)
                _authErrors.value = auth
                false
            }
        }
    

    Lastly, if anyone is curious about how I'm using my LiveData / MutableLiveData objects; I create a private MutableLiveData object and only expose an immutable LiveData object that returns the values of the first object. I do this for better encapsulation / data hiding. Here is an example:

    private val _authErrors: MutableLiveData<AuthErrorFields> by lazy {
            MutableLiveData<AuthErrorFields>()
        }
        val authErrors: LiveData<AuthErrorFields>
            get() { return _authErrors }
    

    Hope this helps someone! 🤗