androidandroid-databinding2-way-object-databinding

Data Binding EditText set null was replaced by set empty String


I have created a sample project which can reproduce this problem.

Expected Behaviour

I have an EditText. I have a TextView which shows the error about the input in this EditText. I also have a reset button which can reset the input inside EditText.

I want to achieve the below:

  1. Use two-way Data Binding
  2. Before user enters anything, no error should be shown.
  3. When user enters something and deleted them, an error saying Input cannot be empty should be shown.
  4. When user clicks Reset, the input inside EditText should be cleared.
  5. When user clicks Reset, there should be no error shown.

How I tried to do it

Layout xml: (For simplicity I am showing only the EditText and TextView here. You can go to the sample project for a full version)

        <EditText
                android:id="@+id/et"
                android:layout_height="wrap_content"
                android:layout_marginEnd="16dp"
                android:layout_marginStart="16dp"
                android:layout_marginTop="16dp"
                android:layout_width="0dp"
                android:text="@={vm.userInput}"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

        <TextView
                android:id="@+id/tvError"
                android:layout_height="wrap_content"
                android:layout_width="0dp"
                app:layout_constraintTop_toBottomOf="@id/et"
                app:layout_constraintStart_toStartOf="@id/et"
                app:layout_constraintEnd_toEndOf="@id/et"
                tools:text="I am some error"
                android:text="@{vm.errorText}"
                android:textColor="@android:color/holo_red_dark"
                android:visibility="@{vm.isErrorVisible() ? View.VISIBLE : View.GONE}" />

The ViewModel:

    val userInput = MutableLiveData<String?>(null)
    val errorText = userInput.map { input ->
        if (input?.isBlank() == true) {
            "This field cannot be empty"
        } else {
            ""
        }
    }
    val isErrorVisible = errorText.map { errorText.value?.isNotBlank() == true }

    fun onReset() {
        userInput.value = null
    }

Observed Behavior

1, 2, 3 are achieved. 4 cannot be achieved - when I click Reset, the error is displayed.

More observations

  1. By debugging, I can observe that userInput.value = "" has been called after onReset(). Probably by the Data Binding library? Which should also be the cause of this whole problem.

  2. If I click reset() when the input is already empty, the error will not be shown. i.e. Point 1 above does not happen if the input is empty.

The Question

How can I achieve 4?


Solution

  • The errorText map function is being executed twice when the reset button is clicked - immediately from the change in onReset() and again, I believe, when the TextView value is updated.

    Other than rethinking the overall design, I suggest that you use the null value and an edit flag to determine why the userInput is blank. Something like the following:

    class MainViewModel : ViewModel() {
        private var isInReset = false
        val userInput = MutableLiveData<String?>(null)
        val errorText = userInput.map { input ->
            // We only care to have an opinion if the input field is not null and blank.
            // Here we determine why it is blank. Is it because it was reset (OK) or because
            // the user deleted all text (not OK).
            if (input == null || input.isNotBlank()) {
                ""
            } else if (isInReset) {
                isInReset = false
                ""
            } else {
                "This field cannot be empty"
            }
        }
    
        val isErrorVisible = errorText.map { errorText.value?.isNotBlank() == true }
    
        fun onReset() {
            isInReset = true
            userInput.value = null
        }
    }