Skip to content

Commit

Permalink
Merge pull request #4 from BenMMcLean/develop
Browse files Browse the repository at this point in the history
First release
  • Loading branch information
emilymclean authored Jul 6, 2023
2 parents 4cf0910 + b008431 commit af0660d
Show file tree
Hide file tree
Showing 37 changed files with 755 additions and 10 deletions.
18 changes: 9 additions & 9 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./build/libs/jsons.jar
asset_name: jsons.jar
asset_content_type: application/java-archive
asset_path: ./forms/build/outputs/aar/forms-release.aar
asset_name: forms.aar
asset_content_type: application/zip
publish-release:
needs: build
if: ${{ github.ref == 'refs/heads/main' }}
Expand Down Expand Up @@ -113,9 +113,9 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./build/libs/jsons.jar
asset_name: jsons.jar
asset_content_type: application/java-archive
asset_path: ./forms/build/outputs/aar/forms-release.aar
asset_name: forms.aar
asset_content_type: application/zip
publish-package:
needs: [build, publish-pre, publish-release]
if: ${{ always() && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') }}
Expand All @@ -128,18 +128,18 @@ jobs:
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
java-version: '17'
distribution: 'temurin'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Build with Gradle
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: -Pversion=${{ needs.build.outputs.version }} build
arguments: -Pversion=${{ needs.build.outputs.version }} forms:build
- name: Publish to GitHub Packages
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: -Pversion=${{ needs.build.outputs.version }} publish
arguments: -Pversion=${{ needs.build.outputs.version }} forms:publish
env:
USERNAME: ${{ github.actor }}
TOKEN: ${{ secrets.GITHUB_TOKEN }}
55 changes: 55 additions & 0 deletions README.md
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.
26 changes: 26 additions & 0 deletions forms/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'maven-publish'
}

android {
Expand Down Expand Up @@ -35,7 +36,32 @@ dependencies {
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'

testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/BenMMcLean/JetpackForms")
credentials {
username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") ?: System.getenv("TOKEN")
}
}
}
publications {
gpr(MavenPublication) {
groupId = "mc.benm.forms"
artifactId = "forms"

afterEvaluate {
from(components["release"])
}
}
}
}
21 changes: 21 additions & 0 deletions forms/src/main/java/cl/benm/forms/Form.kt
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)
}

}

}
7 changes: 7 additions & 0 deletions forms/src/main/java/cl/benm/forms/FormExtractor.kt
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

}
11 changes: 11 additions & 0 deletions forms/src/main/java/cl/benm/forms/FormField.kt
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?

}
7 changes: 7 additions & 0 deletions forms/src/main/java/cl/benm/forms/FormInitialiser.kt
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>)

}
9 changes: 9 additions & 0 deletions forms/src/main/java/cl/benm/forms/LabeledFormField.kt
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?

}
16 changes: 16 additions & 0 deletions forms/src/main/java/cl/benm/forms/Validator.kt
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

}
17 changes: 17 additions & 0 deletions forms/src/main/java/cl/benm/forms/Verifiable.kt
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
}
57 changes: 57 additions & 0 deletions forms/src/main/java/cl/benm/forms/concrete/BaseForm.kt
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 }
}
}
13 changes: 13 additions & 0 deletions forms/src/main/java/cl/benm/forms/concrete/SimpleForm.kt
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 forms/src/main/java/cl/benm/forms/extract/MapFormExtractor.kt
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 forms/src/main/java/cl/benm/forms/extract/PokoFormExtractor.kt
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 forms/src/main/java/cl/benm/forms/fields/CheckboxFormField.kt
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>()
20 changes: 20 additions & 0 deletions forms/src/main/java/cl/benm/forms/fields/DateFormField.kt
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) }
}

}
Loading

0 comments on commit af0660d

Please sign in to comment.