This class which basically does validation on inpu...
# flow
a
This class which basically does validation on input fields. Using
LiveData
to how can i convert the
MediatorLiveData
part to
StateFlow
Copy code
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:
Copy code
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
        )
}
🧵 1
n
Are you looking for
myInputFrom.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:
Copy code
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.