Arpan Sarkar
07/31/2021, 8:18 PMLiveData
to how can i convert the MediatorLiveData
part to StateFlow
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
abstract class InputFrom {
private val _isFromValid = MediatorLiveData<Boolean>()
val isFromValid: LiveData<Boolean>
get() = _isFromValid
abstract val fields: List<LiveInputField<*>>
fun init() {
fields.forEach { inputField ->
_isFromValid.addSource(inputField.value) { changedValue ->
inputField.validate(changedValue)
_isFromValid.value = fields.all {
it.isValid
}
}
}
}
inner class LiveInputField<T>(
private val errorMessage: String? = null,
private val predicate: (T?) -> Boolean
) {
val value = MutableLiveData<T>()
private val _error = MutableLiveData<String>()
val errorText: LiveData<String>
get() = _error
var isValid: Boolean = false
fun validate(value: Any?) {
@Suppress("UNCHECKED_CAST")
return if (predicate(value as? T)) {
_error.value = null
isValid = true
} else {
_error.value = errorMessage
isValid = false
}
}
}
}
Example Usages:
class LoginFrom : InputFrom() {
val username =
LiveInputField<String>("At least 4 characters, only letters, numbers, ., _ allowed.") {
!it.isNullOrBlank() &&
Pattern.compile("[a-zA-Z0-9_.]{4,}")
.matcher(it)
.matches()
}
val password =
LiveInputField<String>(
"8 characters or more, at least one number, one uppercase, one lowercase."
) {
!it.isNullOrBlank()/* &&
Pattern.compile("((?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,})")
.matcher(it)
.matches()*/
}
override val fields: List<LiveInputField<*>>
get() = listOf(
username,
password
)
}
Nick Allen
08/02/2021, 10:31 PMmyInputFrom.isFromValid.asFlow().stateIn(someScope)
or something more like combine(fields.map { field -> field.value.asFlow().map { field.validate() } }) { validateResults -> validateResults.all { it } }
. (second snippet would benefit from breaking out some helper methods.
Honestly, when I look at LiveInputField, I feel like I'd rather see something more like:
class LiveInputField<T>(
private val errorMessage: String? = null,
private val predicate: (T?) -> Boolean
) {
private val _value = MutableStateFlow<T>()
val validOrError: Flow<ValueOrError<T>> = _value
.map {
if (predicate(it) ValidOrError.Valid(it)
else ValidOrError.Error(errorMessage)
}
}
sealed class ValidOrError<T> {
data class Valid(val value: T)
data class Error(val message: String?)
}
You could do the same thing with MutableLiveData
and MediatorLiveData
. The idea is just to expose the validation result itself instead of exposing the pieces and then asking the class's owner to put all the pieces together.