Skip to content

Instantly share code, notes, and snippets.

@objcode
Last active August 6, 2021 05:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save objcode/ef819f6705d006b94b738881df2b41fc to your computer and use it in GitHub Desktop.
Save objcode/ef819f6705d006b94b738881df2b41fc to your computer and use it in GitHub Desktop.
Playing around with form validation in compose
import androidx.compose.*
import androidx.ui.foundation.Text
import androidx.ui.graphics.Color
import androidx.ui.layout.Column
import androidx.ui.material.Button
import androidx.ui.material.Checkbox
import androidx.ui.material.MaterialTheme
import androidx.ui.material.Surface
import androidx.ui.tooling.preview.Preview
// this is really just a thought experiment, not likely useful code
val toggleValidator = Forms.Config<Boolean>(
validator = { it },
message = { "You must toggle the first switch" }
)
@Composable
fun Foo(onSubmit: () -> Unit) {
val (toggled, updateToggled) = state { true }
val form = remember { Forms.Form() }
Surface {
Column {
Checkbox(checked = toggled, onCheckedChange = { updateToggled(it) })
form.onError(toggleValidator, toggled) {
Text(it, style = MaterialTheme.typography.caption.copy(color = Color.Red))
}
Button(onClick = form.handleSubmit(onSubmit)) {
Text("Submit")
}
}
}
}
object Forms {
data class Config<T>(
internal val validator: (T) -> Boolean,
internal val message: (T) -> String,
internal val required: Boolean = false
)
class Validator<T>(value: T, private val config: Config<T>) {
internal var currentValue: T by mutableStateOf(value)
internal var error: String? by mutableStateOf(null)
internal fun onSubmit() {
val localCurrentVale = currentValue
if (isCurrentlyValid(localCurrentVale)) {
error = config.message(localCurrentVale)
} else {
error = null
}
}
private fun isCurrentlyValid(localCurrentValue: T): Boolean {
val missingRequiredValue: Boolean = if (config.required) {
when (localCurrentValue) {
null -> true
is String -> localCurrentValue.isEmpty()
// TODO: make this smarter, configurable, faster, stronger
else -> false
}
} else {
false
}
return missingRequiredValue || !config.validator(localCurrentValue)
}
@Composable
internal fun onError(block: @Composable() (String) -> Unit) {
val localError = error
if (localError != null) {
block(localError)
}
}
}
private val Validator<*>.hasError: Boolean
get() = error != null
class Form {
private val allRegistrations = mutableListOf<Validator<*>>()
@Composable
fun <T> onError(config: Config<T>, value: T, onError: @Composable() (String) -> Unit) {
val state = remember {
val stateToAdd = Validator(value, config)
synchronized(allRegistrations) {
allRegistrations.add(stateToAdd)
}
stateToAdd
}
onCommit(value) {
state.currentValue = value
}
onDispose {
synchronized(allRegistrations) {
allRegistrations.remove(state)
}
}
state.onError(onError)
}
fun handleSubmit(onSubmit: () -> Unit): () -> Unit {
return {
var foundErrors = false
val localRegistrations = synchronized(allRegistrations) {
allRegistrations.toList()
}
for (registration in localRegistrations) {
registration.onSubmit()
foundErrors = foundErrors || registration.hasError
}
if (!foundErrors) {
onSubmit()
}
}
}
}
}
@Preview
@Composable
fun _Foo() = Foo({ })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment