I have created a sample project which can reproduce this problem.
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:
Input cannot be empty
should be shown.Reset
, the input inside EditText
should be cleared.Reset
, there should be no error shown.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
}
1, 2, 3 are achieved. 4 cannot be achieved - when I click Reset
, the error is displayed.
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.
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.
How can I achieve 4?
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
}
}