Basically I have a screen, and there are a few EditText
s and a Button
.
Users have to fill in all fields otherwise the Button
is disabled.
I am using DataBinding
to achieve this. Below is my code in the viewmodel.
val isNextEnabled = MediatorLiveData<Boolean>()
isNextEnabled.apply {
addSource(field1LiveData) {
isNextEnabled.value =
it != null
&& field2LiveData.value != null
&& field3LiveData.value != null
}
addSource(field2LiveData) {
isNextEnabled.value =
it != null
&& field1LiveData.value != null
&& field3LiveData.value != null
}
addSource(field3LiveData) {
isNextEnabled.value =
it != null
&& field2LiveData.value != null
&& field1LiveData.value != null
}
}
In the xml
<Button
android:enabled="@{viewmodel.isNextEnabled}"
.
.
.
</Button>
Everything works fine as expected. But the logic above looks cumbersome. What if I have more EditText
? The code would be painful to write/maintain.
Is there any way I can simplify it?
Ultimately you have a UseCase/Logic where you decide when the next
button is enabled.
I think you should separate the logic into useCases where it makes sense.
E.g.
// update these when they change in the UI for e.g.
val field1Flow: Flow<Boolean> = flow { ... }
val field2Flow: Flow<Boolean> = flow { ... }
val nextButtonState = combine(field1Flow, field2Flow) { f1, f2 ->
f1 && f2
}.collect { state ->
// use your state.
}
Now... if you need special logic and not just two-boolean algebra here, you can always extract it into use-cases that return more flows.
Or map
it or various operations you could do:
E.g.
class YourUseCase() {
operator fun invoke(field1: Boolean, field2: Boolean) {
// Your Logic
return field1 && field2
}
}
// And now...
val _nextButtonState = combine(field1Flow, field2Flow) { f1, f2 ->
YourUseCase(f1, f2)
}
val _uiState = _nextButtonState.transformLatest {
emit(it) // you could add a when(it) { } and do more stuff here
}
// And if you don't want to change your UI to use flows, you can expose this as live data
val uiState = _uiState.asLiveData()
Keep in mind this is Pseudo-code written on SO.. not even Notepad ;)
I hope that makes a bit of sense. The idea is to separate the bits into use-cases (that you can ultimately test in isolation) and to have a flow of data. When buttons change state, the fieldNFlow emits the values and this triggers the whole chain for you.
If you have the latest Coroutines (2.4.0+) you can use the new operators to avoid using LiveData, but overall, I'd try to think in that direction.
Lastly, your liveData code with a mediator is not bad, I'd at the very least, extract the "logic" into 3 different useCases so it's not all together in a series of if/else statements.
A word of caution: I haven't used Databinding in over 3(?) years, I'm personally not a fan of it so I cannot tell you if it would cause a problem with this approach.