4

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?

Sira Lam
  • 5,179
  • 3
  • 34
  • 68

1 Answers1

1

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
    }
}
Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • Hello Cheticamp! It's you again to answer my question :D To be honest I have this solution in mind, but it is kind of the last resort, I want to see if there is any non-hacking way to do this... Anyway, if there are no better answer in coming few days, I will just accept your answer with bounty – Sira Lam May 07 '21 at 01:39
  • @SiraLam That would come under "rethinking the overall design." This type of error is usually shown when the user moves out of the field. That way there would be no ambiguity about how to handle the field when it is empty. – Cheticamp May 07 '21 at 11:18
  • that's also a good suggestion. I would consider it thanks! – Sira Lam May 07 '21 at 11:47
  • 1
    @SiraLam When the field is set to an empty string and the `errorText` code is executed, the question will always be "why is this value blank?" The flag tells why it is blank. I will be interested if anyone has a way to do what you ask without some sort of flag or state change. – Cheticamp May 07 '21 at 13:35