Slide 1

Slide 1 text

gerard.paligot.com BUILDING DYNAMIC FORMS WITH JSONFORMS AND KMP

Slide 2

Slide 2 text

WHAT IS THE ISSUE?

Slide 3

Slide 3 text

France Colombia

Slide 4

Slide 4 text

THE CHALLENGE ▸ Maintenance overhead ▸ Slow rollouts ▸ Inconsistent UI/UX ▸ Scaling difficult

Slide 5

Slide 5 text

A BETTER WAY ▸ Standard to describe a form, not specific to a need ▸ Use a declarative approach, not hard-coded ▸ Flexible UI rendering, driven by the logic ▸ Maintainable & scalable

Slide 6

Slide 6 text

JSONFORMS PRESENTATION

Slide 7

Slide 7 text

THE BUILDING BLOCKS Schema Data structure & Rules UiSchema Layout & Appearance Data Current values JSONForms Component / Renderer

Slide 8

Slide 8 text

LET’S BUILD A FORM!

Slide 9

Slide 9 text

EXPLORING FEATURES JSONFORMS

Slide 10

Slide 10 text

EXPLORING JSONFORMS FEATURES ▸ Dynamic visibility

Slide 11

Slide 11 text

@Serializable data class Rule( val effect: Effect, val condition: Condition ) enum class Effect { Hide, Show, Disable, Enable } @Serializable data class Condition( val scope: String, val schema: ConditionSchema ) @Serializable data class ConditionSchema( val const: JsonPrimitive? = null, val enum: ImmutableList? = null, val not: ConditionSchema? = null, val pattern: String? = null )

Slide 12

Slide 12 text

DEMO TIME

Slide 13

Slide 13 text

EXPLORING JSONFORMS FEATURES ▸ Dynamic visibility ▸ Custom controls

Slide 14

Slide 14 text

@Composable fun RendererStringScope.Material3StringProperty( value: String?, modifier: Modifier = Modifier, error: String? = null, onValueChange: (String) !" Unit ) { !!# } @Composable fun RendererNumberScope.Material3NumberProperty( value: String?, modifier: Modifier = Modifier, error: String? = null, onValueChange: (String) !" Unit ) { !!# } @Composable fun RendererBooleanScope.Material3BooleanProperty( value: Boolean, modifier: Modifier = Modifier, onValueChange: (Boolean) !" Unit ) { !!# } @Composable fun RendererLayoutScope.Material3Layout( modifier: Modifier = Modifier, content: @Composable (UiSchema) !" Unit ) { !!# }

Slide 15

Slide 15 text

@Composable fun RendererBooleanScope.Material3BooleanProperty( value: Boolean, modifier: Modifier = Modifier, onValueChange: (Boolean) !" Unit ) { when { isToggle() !" Switch( value = value, modifier = modifier, label = label(), description = description(), enabled = enabled(), onCheckedChange = onValueChange ) else !" Checkbox( value = value, modifier = modifier, label = label(), enabled = enabled(), onCheckedChange = onValueChange ) } } @Stable interface RendererBooleanScope { fun isToggle(): Boolean fun label(): String? fun description(): String? fun enabled(): Boolean }

Slide 16

Slide 16 text

@Composable internal fun StringProperty( control: Control, schemaProvider: SchemaProvider, jsonFormState: JsonFormState, content: @Composable RendererStringScope.(id: String) !" Unit ) { val scope = remember(control) { RendererStringScopeInstance(control, schemaProvider, jsonFormState) } scope.content(control.propertyKey()) }

Slide 17

Slide 17 text

@Composable internal fun Property( control: Control, schemaProvider: SchemaProvider, jsonFormState: JsonFormState, stringContent: @Composable (RendererStringScope.(id: String) !" Unit), numberContent: @Composable (RendererNumberScope.(id: String) !" Unit), booleanContent: @Composable (RendererBooleanScope.(id: String) !" Unit) ) { when (schemaProvider.getPropertyByControl(control)) { is StringProperty !" StringProperty( control = control, schemaProvider = schemaProvider, jsonFormState = jsonFormState, content = stringContent ) is BooleanProperty !" BooleanProperty( control = control, schemaProvider = schemaProvider, jsonFormState = jsonFormState, content = booleanContent ) is NumberProperty !" NumberProperty( control = control, schemaProvider = schemaProvider, jsonFormState = jsonFormState, content = numberContent ) is ObjectProperty !" error("Object property can't be specified in the layout") is ArrayProperty !" TODO() } }

Slide 18

Slide 18 text

@Composable fun JsonForm( schema: Schema, uiSchema: UiSchema, modifier: Modifier = Modifier, state: JsonFormState = rememberJsonFormState(initialValues = mutableMapOf()), layoutContent: @Composable (RendererLayoutScope.(@Composable (UiSchema) !" Unit) !" Unit), stringContent: @Composable (RendererStringScope.(id: String) !" Unit), numberContent: @Composable (RendererNumberScope.(id: String) !" Unit), booleanContent: @Composable (RendererBooleanScope.(id: String) !" Unit) ) { val schemeProvider = rememberSchemeProvider(uiSchema = uiSchema, schema = schema) Box(modifier = modifier) { Layout( uiSchema = uiSchema, jsonFormState = state, layoutContent = layoutContent, content = { control !" Property( control = control, schemaProvider = schemeProvider, jsonFormState = state, stringContent = stringContent, numberContent = numberContent, booleanContent = booleanContent ) } ) } }

Slide 19

Slide 19 text

JsonForm( schema = schema, uiSchema = uiSchema, state = state, layoutContent = { Material3Layout(content = it) }, stringContent = { id !" val value = state[id].value as String? val error = state.error(id = id).value Material3StringProperty( value = value, error = error!$message, onValueChange = { state[id] = it } ) }, numberContent = {}, booleanContent = {} )

Slide 20

Slide 20 text

DEMO TIME

Slide 21

Slide 21 text

EXPLORING JSONFORMS FEATURES ▸ Dynamic visibility ▸ Custom controls ▸ External data interaction

Slide 22

Slide 22 text

DEMO TIME

Slide 23

Slide 23 text

JSONFORMS ARCHITECTURE

Slide 24

Slide 24 text

ARCHITECTURE CORE VS RENDERER

Slide 25

Slide 25 text

OUR KOTLIN MULTIPLATFORM ARCHITECTURE JSONForms shared KMP ui Material3 Renderer material3 Your Renderer Set Custom Renderer

Slide 26

Slide 26 text

ROAD TO COMPOSE MULTIPLATFORM WITH CUPERTINO RENDERER

Slide 27

Slide 27 text

OUR KOTLIN MULTIPLATFORM ARCHITECTURE JSONForms shared KMP ui Material3 Renderer material3 Your Renderer Set Custom Renderer Apple Renderer cupertino Custom Renderer

Slide 28

Slide 28 text

kotlin { !!# sourceSets { commonMain.dependencies { api(projects.ui) implementation(compose.ui) implementation(compose.foundation) implementation(libs.cupertino) } } }

Slide 29

Slide 29 text

@Composable fun RendererStringScope.CupertinoStringProperty( value: String?, modifier: Modifier = Modifier, error: String? = null, onValueChange: (String) !" Unit ) { !!# } @Composable fun RendererNumberScope.CupertinoNumberProperty( value: String?, modifier: Modifier = Modifier, error: String? = null, onValueChange: (String) !" Unit ) { !!# } @Composable fun RendererBooleanScope.CupertinoBooleanProperty( value: Boolean, modifier: Modifier = Modifier, onValueChange: (Boolean) !" Unit ) { !!# } @OptIn(ExperimentalCupertinoApi!%class) @Composable fun RendererLayoutScope.CupertinoLayout( modifier: Modifier = Modifier, content: @Composable (UiSchema) !" Unit ) { !!# }

Slide 30

Slide 30 text

@Composable fun RendererStringScope.CupertinoStringProperty( value: String?, modifier: Modifier = Modifier, error: String? = null, onValueChange: (String) !" Unit ) { when { isRadio() !" SegmentedControl( !!# ) isDropdown() !" WheelPicker( !!# ) else !" OutlinedTextField( !!# ) } }

Slide 31

Slide 31 text

DEMO TIME

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

IN CONCLUSION

Slide 34

Slide 34 text

IN CONCLUSION ▸ Standardize complex forms with JSONForms implementation ▸ Respect the JSONForms architecture for customization purpose ▸ Deliver a native user experience ▸ Accelerate development and our rollout ▸ Improve maintainability & scalability

Slide 35

Slide 35 text

REFERENCES ▸ JSONForms standard https://jsonforms.io/ ▸ jsonforms-kotlin https://github.com/GerardPaligot/jsonforms-kotlin ▸ compose-cupertino https://github.com/schott12521/compose-cupertino

Slide 36

Slide 36 text

THANK YOU! gerard.paligot.com