-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from BenMMcLean/develop
First release
- Loading branch information
Showing
37 changed files
with
755 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# JetpackForms | ||
[![Build](https://github.com/BenMMcLean/JetpackForms/actions/workflows/build.yml/badge.svg)](https://github.com/BenMMcLean/JetpackForms/actions/workflows/build.yml) | ||
|
||
A Jetpack first forms library. | ||
|
||
JetpackForms compartmentalizes data management and validation logic out of ViewModels and into | ||
reusable field and validation objects. | ||
|
||
## Setup | ||
JetpackForms automatically publishes to Github Packages. To use the core library in your project, | ||
add the registry to your settings.gradle file: | ||
```groovy | ||
pluginManagement { | ||
repositories { | ||
maven { | ||
url = uri("https://maven.pkg.github.com/BenMMcLean/JetpackForms") | ||
} | ||
} | ||
} | ||
``` | ||
then add your dependency to your app level build.gradle file: | ||
```groovy | ||
dependencies { | ||
implementation 'cl.benm.forms:forms:LATEST_VERSION' | ||
} | ||
``` | ||
|
||
## Usage | ||
To use the forms library, create a new form with any of the fields: | ||
```kotlin | ||
val form = Form.of(listOf( | ||
TextFormField( | ||
"name", | ||
listOf(RequiredValidator()), | ||
TextFormFactor.TEXT | ||
), | ||
TextFormField.phone("phone", true) // Some fields have shortcut constructors for common data types | ||
)) | ||
``` | ||
fields can then be accessed with: | ||
```kotlin | ||
form.getField<String>("name") | ||
``` | ||
|
||
Out of the box, JetpackForms can also extract data from form fields into a map or flat Kotlin object: | ||
```kotlin | ||
val contentAsAMap = form.extract(MapFormExtractor()) | ||
val contentAsAPoko = form.extract(PokoFormExtractor( | ||
target = NameAndPhone::class | ||
)) | ||
``` | ||
|
||
## Future | ||
In the future, a compose library is planned that would provide a simple and automatic interface | ||
for rendering form fields is planned. In addition, support for file selectors is planned. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package cl.benm.forms | ||
|
||
import cl.benm.forms.concrete.SimpleForm | ||
|
||
interface Form: Verifiable { | ||
|
||
suspend fun initialize(initializer: FormInitialiser) | ||
suspend fun <T> extract(extractor: FormExtractor<T>) | ||
|
||
fun <T> getField(name: String): FormField<T> | ||
fun hasField(name: String): Boolean | ||
|
||
companion object { | ||
|
||
fun of(fields: List<FormField<*>>): Form { | ||
return SimpleForm(fields) | ||
} | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package cl.benm.forms | ||
|
||
interface FormExtractor<T> { | ||
|
||
suspend fun extract(fields: List<FormField<*>>): T | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package cl.benm.forms | ||
|
||
import kotlinx.coroutines.flow.Flow | ||
|
||
interface FormField<T>: Verifiable { | ||
|
||
val name: String | ||
val value: Flow<T?> | ||
var currentValue: T? | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package cl.benm.forms | ||
|
||
interface FormInitialiser { | ||
|
||
suspend fun <T> init(field: FormField<T>) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package cl.benm.forms | ||
|
||
import android.content.Context | ||
|
||
interface LabeledFormField<T> { | ||
|
||
fun getLabel(value: T?, context: Context): String? | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package cl.benm.forms | ||
|
||
interface Validator<in T> { | ||
|
||
fun validate(input: T?): ValidationResult | ||
|
||
} | ||
|
||
sealed interface ValidationResult { | ||
|
||
object Valid: ValidationResult | ||
class Invalid( | ||
val message: String? | ||
): ValidationResult | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package cl.benm.forms | ||
|
||
import kotlinx.coroutines.flow.Flow | ||
|
||
interface Verifiable { | ||
|
||
val valid: Flow<ValidationState> | ||
suspend fun validate(silent: Boolean = false): Boolean | ||
|
||
} | ||
|
||
sealed interface ValidationState { | ||
object Valid: ValidationState | ||
class Invalid( | ||
val message: String? | ||
): ValidationState | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package cl.benm.forms.concrete | ||
|
||
import cl.benm.forms.Form | ||
import cl.benm.forms.FormExtractor | ||
import cl.benm.forms.FormField | ||
import cl.benm.forms.FormInitialiser | ||
import cl.benm.forms.ValidationState | ||
import cl.benm.forms.validators.multi.FormInjectable | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.combine | ||
import java.lang.ref.WeakReference | ||
|
||
abstract class BaseForm: Form { | ||
|
||
protected abstract val fields: List<FormField<*>> | ||
|
||
protected fun afterFieldsEntered() { | ||
fields.forEach { | ||
if (it is FormInjectable) { | ||
it.injectForm(WeakReference(this)) | ||
} | ||
} | ||
} | ||
|
||
override suspend fun initialize(initializer: FormInitialiser) { | ||
fields.forEach { | ||
initializer.init(it) | ||
} | ||
} | ||
|
||
override suspend fun <T> extract(extractor: FormExtractor<T>) { | ||
extractor.extract(fields) | ||
} | ||
|
||
override fun <T> getField(name: String): FormField<T> { | ||
return fields.first { it.name == name } as FormField<T> | ||
} | ||
|
||
override fun hasField(name: String): Boolean { | ||
return fields.any { it.name == name } | ||
} | ||
|
||
override val valid: Flow<ValidationState> | ||
get() = combine(fields.map { it.valid }) { | ||
it.fold<ValidationState, ValidationState>(ValidationState.Valid) { acc, v -> | ||
when { | ||
acc is ValidationState.Invalid -> acc | ||
v is ValidationState.Invalid -> v | ||
else -> ValidationState.Valid | ||
} | ||
} | ||
} | ||
|
||
override suspend fun validate(silent: Boolean): Boolean { | ||
return fields.map { it.validate(silent) }.all { it } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package cl.benm.forms.concrete | ||
|
||
import cl.benm.forms.FormField | ||
|
||
class SimpleForm( | ||
override val fields: List<FormField<*>> | ||
): BaseForm() { | ||
|
||
init { | ||
afterFieldsEntered() | ||
} | ||
|
||
} |
14 changes: 14 additions & 0 deletions
14
forms/src/main/java/cl/benm/forms/extract/MapFormExtractor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package cl.benm.forms.extract | ||
|
||
import cl.benm.forms.FormExtractor | ||
import cl.benm.forms.FormField | ||
|
||
class MapFormExtractor: FormExtractor<Map<String, Any?>> { | ||
|
||
override suspend fun extract(fields: List<FormField<*>>): Map<String, Any?> { | ||
return fields.associate { | ||
val name = it.name | ||
name to it.currentValue | ||
} | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
forms/src/main/java/cl/benm/forms/extract/PokoFormExtractor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package cl.benm.forms.extract | ||
|
||
import cl.benm.forms.FormExtractor | ||
import cl.benm.forms.FormField | ||
import kotlin.reflect.KClass | ||
import kotlin.reflect.KParameter | ||
|
||
class PokoFormExtractor<T: Any>( | ||
val target: KClass<T> | ||
): FormExtractor<T> { | ||
|
||
override suspend fun extract(fields: List<FormField<*>>): T { | ||
val map = fields.associate { it.name.lowercase() to it.currentValue } | ||
val constructor = target.constructors.last() | ||
val args: Map<KParameter, Any?> = constructor.parameters.associateWith { map[it.name?.lowercase()] } | ||
return constructor.callBy(args) | ||
} | ||
|
||
} |
9 changes: 9 additions & 0 deletions
9
forms/src/main/java/cl/benm/forms/fields/CheckboxFormField.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package cl.benm.forms.fields | ||
|
||
import cl.benm.forms.Validator | ||
import cl.benm.forms.fields.base.InputFormField | ||
|
||
class CheckboxFormField( | ||
override val name: String, | ||
override val validators: List<Validator<Boolean>> | ||
): InputFormField<Boolean>() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package cl.benm.forms.fields | ||
|
||
import android.content.Context | ||
import cl.benm.forms.LabeledFormField | ||
import cl.benm.forms.Validator | ||
import cl.benm.forms.fields.base.InputFormField | ||
import java.text.DateFormat | ||
import java.util.* | ||
|
||
class DateFormField( | ||
override val name: String, | ||
override val validators: List<Validator<Date>>, | ||
private val dateFormat: DateFormat | ||
): InputFormField<Date>(), LabeledFormField<Date> { | ||
|
||
override fun getLabel(value: Date?, context: Context): String? { | ||
return value?.let { dateFormat.format(it) } | ||
} | ||
|
||
} |
Oops, something went wrong.