SlideShare a Scribd company logo
1 of 103
Download to read offline
FormsKitDavid Rodrigues,@dmcrodrigues
Babylon Health
1
Forms? !
2
3
Butbefore starting some
context...
4
Managing state can bean
extremelyhardtask
» Mutability
» Observation
» Thread-safety
5
To buildaformweare
especiallyinterested in
mutabilityand observation
6
Thread-safetyisalso
fundamentalbut outofscope
forthistalk
7
final class Property<Value> {
private var handlers: [(Value) -> Void] = []
var value: Value {
didSet { handlers.forEach { $0(value) } }
}
init(value: Value) {
self.value = value
}
func observe(_ handler: @escaping (Value) -> Void) {
self.handlers.append(handler)
handler(value)
}
func map<T>(_ transform: @escaping (Value) -> T) -> Property<T> {
let property = Property<T>(value: transform(value))
observe { [weak property] value in property?.value = transform(value) }
return property
}
}
8
⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
Please notethis isafairly
simple implementation !
⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
9
Propertygive usanicewayto observe
changes
1> let a = Property(value: 1)
2> a.observe { value in print("Value: (value)") }
"Value: 1"
3> a.value = a.value + 2
"Value: 3"
10
Andto derive newstates
1> let a = Property(value: 1.0)
2> let b = a.map { value in "(value) as String" }
4> b.value
"1.0 as String"
5> a.value = a.value * 10
6> a.value
10.0
7> b.value
"10.0 as String"
11
Backto forms...
12
13
// Sign-Up Table View DataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 12
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
// Facebook button
case 1:
// Or text
case 2:
// First Name
case 3:
// Last Name
...
}
}
14
Adding or removing elements requires:
» update the total number of elements
» shift all indices affected
Moving elements requires:
» shift all indices affected
15
Stillmanageable?
16
17
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
// Account selected
case 1:
switch consultantType {
case .gp:
switch dateAndTimeAvailable {
case true:
...
case false:
...
}
case .specialist:
...
}
case ???:
...
...
}
}
18
Buthowcanwe getthose
fancyanimationswhen
something changes?
19
20
Index pathsare
aweak system,
hard to manage
and maintain21
Especially with
dynamic changes
22
Whatthen?
23
Let's forgeteverythingand
startfrom scratch...
24
What'saform?
25
Aform isacollection
of components following
asequential order
26
27
A form isa collection
ofcomponents following
a sequentialorder
28
Colection ofcomponents
withasequentialorder?
!
29
ArrayAn ordered, random-access collection
1> let array = [0, 1, 2]
2> array
[
0,
1,
2
]
30
We can representaform
withacollection of
components
31
» Text
» Text Input
» Password
» Selection
» Buttons
» ...
32
enum Component {
case text(String)
case textInput(placeholder: String, ...)
case password(placeholder: String, ...)
case selection(...)
case button(title: String, ...)
case facebookButton(...)
case toggle(title: String, ...)
...
}
33
let components: [Component] = [
.facebookButton(...), // Row 0
.text("OR"), // Row 1
.textInput(placeholder: "First name", ...), // Row 2
.textInput(placeholder: "Last name", ...), // Row 3
.textInput(placeholder: "Email", ...), // Row 4
.password(placeholder: "Password", ...), // Row 5
...
]
34
Byusinganorderedcollectionwecan
derive therespective
state (indexpaths)witha
clear and
declarative way 35
Adding, removing or moving
elements is super easy✨
let components: [Component] = [
.textInput(placeholder: "First name", ...), // Row 0
.textInput(placeholder: "Last name", ...), // Row 1
.textInput(placeholder: "Email", ...), // Row 2
.password(placeholder: "Password", ...), // Row 3
...
]
36
Buthow dowe go
fromacollection of
componentstoa
form? !
37
protocol Form {
var components: Property<[FormComponent]> { get }
}
protocol FormComponent {
func matches(with component: FormComponent) -> Bool
func render() -> UIView & FormComponentView
}
protocol FormComponentView {
...
}
enum Component: FormComponent {
...
}
38
⚠ Disclaimer ⚠
The following examplesare
based on MVVM
39
40
Butthis can be
appliedtoanything,
including MVC ifyou
are wondering !
41
class SignUpViewModel: Form {
let components: Property<[Component]>
init(...) {
self.components = Property(value: [
.facebookButton(...),
.text("OR"),
.textInput(placeholder: "First name", ...),
.textInput(placeholder: "Last name", ...),
.textInput(placeholder: "Email", ...),
.password(placeholder: "Password", ...),
...
])
}
}
42
final class FormTableViewDataSource: NSObject, UITableViewDataSource {
private var currentComponents: [FormComponent] = []
init(tableView: UITableView, components: Property<[FormComponent]>) {
components.observe { components in
self.currentComponents = components
tableView.reloadData()
}
}
}
open class FormViewController<F: Form>: UIViewController {
private let tableView = UITableView()
private let dataSource: FormTableViewDataSource
init(form: F) {
dataSource = FormTableViewDataSource(
tableView: tableView,
components: form.components
)
...
}
}
43
final class SignUpViewController: FormViewController {
init(viewModel: SignUpViewModel) {
super.init(form: viewModel)
}
}
44
Ok butwhatifwe needa
dynamic collection of
components? !
45
Quick Example:
Sign-Inwithtwotypes ofauthentication
» Email + Password
» Email + Membership ID
46
47
enum SignInType {
case standard
case membership
}
class SignInViewModel: Form {
let components: Property<[Component]>
init(...) {
self.components = ??? !
}
}
48
switch signInType {
case .standard:
components = Property(value: [
.textInput(placeholder: "Email", ...),
.password(placeholder: "Password", ...),
.toggle(title: "Sign-In with Membership ID", ...)
.button(title: "Submit", ...)
])
case .membership:
components = Property(value: [
.textInput(placeholder: "Email", ...),
.toggle(title: "Sign-In with Membership ID", ...)
.textInput(placeholder: "Membership ID", ...),
.button(title: "Submit", ...)
])
}
49
Firstwe needatriggerto change from one
statetothe other
.toggle(title: "Sign-In with Membership ID", ...)
50
enum Component: FormComponent {
...
case toggle(title: String, isOn: Property<Bool>)
...
}
Nowwe can define component's initialvalue
and observeanychanges
51
enum SignInType {
case standard
case membership
}
class SignInViewModel: Form {
private isMembershipSelected = Property(value: false)
let components: Property<[Component]>
init(...) {
self.components = ??? !
}
}
52
enum SignInType { case standard, membership }
class SignInViewModel: Form {
private isMembershipSelected = Property(value: false)
private signInType: Property<SignInType>
let components: Property<[Component]>
init(...) {
self.signInType = isMembershipSelected.map { isSelected in
return isSelected ? .membership : .standard
}
self.components = ??? !
}
}
53
enum SignInType { case standard, membership }
class SignInViewModel: Form {
private isMembershipSelected = Property(value: false)
private signInType: Property<SignInType>
let components: Property<[Component]>
init(...) {
self.signInType = ...
self.components = signInType.map { signInType in
switch signInType {
case .standard:
...
case .membership:
...
}
}
}
}
54
self.components = signInType.map { signInType in
switch signInType {
case .standard: return [
...
.toggle(
title: "Sign-In with Membership ID",
isOn: isMembershipSelected
)
...
]
case .membership: return [
...
.toggle(
title: "Sign-In with Membership ID",
isOn: isMembershipSelected
)
...
]
}
}
55
56
switch signInType {
case .standard:
components = Property(value: [
.textInput(placeholder: "Email", ...),
...
.toggle(title: "Sign-In with Membership ID", ...)
.button(title: "Submit", ...)
])
case .membership:
components = Property(value: [
.textInput(placeholder: "Email", ...),
.toggle(title: "Sign-In with Membership ID", ...)
...
.button(title: "Submit", ...)
])
}
57
Weare repeatingthe same
components for each
state... can'twe compose
this inabetterway?
58
Wearerepeatingthesamecomponents
foreachstate...can'twe compose
thisinabetterway?
59
FormBuilder
60
struct FormBuilder<Component: FormComponent> {
let components: [Component]
public static var empty: FormBuilder {
return FormBuilder()
}
init(components: [Component] = []) {
self.components = components
}
}
61
We havethe container, now
we onlyneed methodsto
compose
62
Butwhymethodswhenwe
have operators?
63
precedencegroup ChainingPrecedence {
associativity: left
higherThan: TernaryPrecedence
}
// Compose with a new component
infix operator |-+ : ChainingPrecedence
// Compose with another builder
infix operator |-* : ChainingPrecedence
64
struct FormBuilder {
...
static func |-+(
builder: FormBuilder,
component: Component
) -> FormBuilder {
...
}
static func |-* (
builder: FormBuilder,
components: [Component]
) -> FormBuilder {
...
}
}
65
func signInStandardComponents(for signInType: SignInType) -> FormBuilder {
guard let signInType == .standard else { return .empty }
return FormBuilder.empty
|-+ .password(placeholder: "Password", ...)
}
func signInMembershipComponents(for signInType: SignInType) -> FormBuilder {
guard let signInType == .standard else { return .empty }
return FormBuilder.empty
|-+ .textInput(placeholder: "Membership ID", ...)
}
self.components = signInType.map { signInType in
let builder = FormBuilder<Component>.empty
|-+ .textInput(placeholder: "Email", ...)
|-* signInStandardComponents(for: signInType)
|-+ .toggle(title: "Sign-In with Membership ID", isOn: isMembershipSelected)
|-* signInMembershipComponents(for: signInType)
|-+ .button(title: "Submit", ...)
return builder.components
}
66
Composingabuilderwithanother
builder is powerfulbutmaybewe can
have something simpler
67
// Compose components pending on a boolean condition
infix operator |-? : ChainingPrecedence
struct FormBuilder<Component: FormComponent> {
...
static func |-? (
builder: FormBuilder,
validator: Validator<FormBuilder<Component>>
) -> FormBuilder {
...
}
}
struct Validator<T> {
// `iff` stands for "if and only if" from math and logic
static func iff(
_ condition: @autoclosure @escaping () -> Bool,
generator: @escaping (T) -> T
) -> Validator<T> {
...
}
}
68
self.components = signInType.map { signInType in
let builder = FormBuilder<Component>.empty
|-+ .textInput(placeholder: "Email", ...)
|-? .iff(signInType == .standard) {
$0 |-+ .password(placeholder: "Password", ...)
}
|-+ .toggle(title: "Sign-In with Membership ID", isOn: isMembershipSelected)
|-? .iff(signInType == .membership) {
$0 |-+ .textInput(placeholder: "Membership ID", ...)
}
|-+ .button(title: "Submit", ...)
return builder.components
}
69
Nowwe haveadynamic collection of
componentsand consequentlya
dynamic rendering
70
Nowwehaveadynamiccollectionofcomponentsand
consequentlyadynamic rendering !
71
We can definearendererto
renderthe collection of
components for each state
72
protocol Renderer {
associatedtype FormState
func render(state: FormState) -> [FormComponent]
}
struct SignInRenderer: Renderer {
func render(state: SignInType) -> [FormComponent] {
let builder = FormBuilder<Component>.empty
|-+ .textInput(placeholder: "Email", ...)
|-? .iff(signInType == .standard) {
$0 |-+ .password(placeholder: "Password", ...)
}
|-+ .toggle(title: "Sign-In with Membership ID", isOn: isMembershipSelected)
|-? .iff(signInType == .membership) {
$0 |-+ .textInput(placeholder: "Membership ID", ...)
}
|-+ .button(title: "Submit", ...)
return builder.components
}
}
73
enum SignInType { case standard, membership }
class SignInViewModel: Form {
private isMembershipSelected = Property(value: false)
private signInType: Property<SignInType>
let components: Property<[Component]>
init(...) {
self.signInType = isMembershipSelected.map { isSelected in
return isSelected ? .membership : .standard
}
let renderer = SignInRenderer(
isMembershipSelected: isMembershipSelected,
...
)
self.components = signInType.map(renderer.render(state:))
}
}
74
Andwiththis canwe have
fancyanimations? !
75
YES76
final class FormTableViewDataSource: NSObject, UITableViewDataSource {
private var currentComponents: [FormComponent] = []
init(tableView: UITableView, components: Property<[FormComponent]>) {
components.observe { components in
self.currentComponents = components
tableView.reloadData() // !
}
}
}
77
WELL
NOT
YET 78
Everytime our state
changes,anewcollection
ofcomponents is
generatedto reflectthat
particular state
79
1> (signInViewModel.signInType, signInViewModel.components)
(SignInType.standard, [
.textInput(placeholder: "Email", ...),
.password(placeholder: "Password", ...),
.toggle(title: "Sign-In with Membership ID", ...),
.button(title: "Submit", ...)
])
2> (signInViewModel.signInType, signInViewModel.components)
(SignInType.membership, [
.textInput(placeholder: "Email", ...),
.toggle(title: "Sign-In with Membership ID", ...),
.textInput(placeholder: "Membership ID", ...),
.button(title: "Submit", ...)
])
80
1> (signInViewModel.signInType, signInViewModel.components)
// (SignInType.standard, [
// .textInput(placeholder: "Email", ...),
.password(placeholder: "Password", ...),
// .toggle(title: "Sign-In with Membership ID", ...),
// .button(title: "Submit", ...)
//])
2> (signInViewModel.signInType, signInViewModel.components)
// (SignInType.membership, [
// .textInput(placeholder: "Email", ...),
// .toggle(title: "Sign-In with Membership ID", ...),
.textInput(placeholder: "Membership ID", ...),
// .button(title: "Submit", ...)
//])
81
SignInType.standard ➡ SignInType.membership
» REMOVES (-)
.password(placeholder: "Password", ...)
» INSERTS (+)
.textInput(placeholder: "Membership ID", ...)
82
SignInType.membership ➡ SignInType.standard
» REMOVES (-)
.textInput(placeholder: "Membership ID", ...)
» INSERTS (+)
.password(placeholder: "Password", ...)
83
Rememberthis?
protocol FormComponent {
func matches(with component: FormComponent) -> Bool
}
84
Givenacertain component
we can match itagainst
another componentto
validate iftheyare
equivalentor not
85
1> let componentA = Component.password(placeholder: "Password", ...)
2> let componentB = Component.password(placeholder: "Password", ...)
3> let componentC = Component.textInput(placeholder: "Membership ID", ...)
4> componentA.matches(with: componentA)
true
5> componentA.matches(with: componentB)
true
6> componentA.matches(with: componentC)
false
86
Then byemployingadiffing
algorithmwe can identify
anychanges betweentwo
collections ofcomponents
87
import Dwifft
static func diff<Value>(
_ lhs: [Value],
_ rhs: [Value]
) -> [DiffStep<Value>] where Value: Equatable
88
import Dwifft
static func diff<Value>(
_ lhs: [Value],
_ rhs: [Value]
) -> [DiffStep<Value>] where Value: Equatable
enum DiffStep<Value> {
case insert(Int, Value)
case delete(Int, Value)
}
89
import Dwifft
static func diff<Value>(
_ lhs: [Value],
_ rhs: [Value]
) -> [DiffStep<Value>] where Value: Equatable
enum DiffStep<Value> {
case insert(Int, Value)
case delete(Int, Value)
}
Value: Equatable !
90
struct FormNode: Equatable {
let component: FormComponent
static func ==(left: FormNode, right: FormNode) -> Bool {
return left.component.matches(with: right.component)
}
}
91
struct FormBuilder<Component: FormComponent> {
...
func build() -> [FormNode] {
return components.map(FormNode.init)
}
}
protocol Form {
var nodes: Property<[FormNode]> { get }
}
92
final class FormTableViewDataSource: NSObject, UITableViewDataSource {
private var currentNodes: [FormNode] = []
init(tableView: UITableView, nodes: Property<[FormNode]>) {
nodes.observe { nodes in
self.currentNodes = nodes
tableView.reloadData()
}
}
}
93
nodes.observe { nodes in
let previousNodes = self.currentNodes
self.currentNodes = nodes
tableView.beginUpdates()
for step in Dwifft.diff(previousNodes, currentNodes) {
switch step {
case let .insert(index, _):
tableView.insertRows(
at: [IndexPath(row: index, section: 0)], with: .fade)
case let .delete(index, _):
tableView.deleteRows(
at: [IndexPath(row: index, section: 0)], with: .fade)
}
}
tableView.endUpdates()
}
94
Timeto recap
95
96
The conceptis highlyinspired on React
from Facebook
97
⏰ Timeto close ⏰
98
Managing state is hard but
we cantryto minimise how
hard itbecomes
99
State derivation is
essentialto reduce
mutabilityandachievea
self-descriptive system
100
FormsKit's ultimate goalis
to helpcreatingand
managinganykind ofform
Openforextension
101
Open source
January'18102
Thank
You ! 103

More Related Content

What's hot

Data20161007
Data20161007Data20161007
Data20161007capegmail
 
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night TurinAsync code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night TurinFabio Collini
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form componentSamuel ROZE
 
Architectures in the compose world
Architectures in the compose worldArchitectures in the compose world
Architectures in the compose worldFabio Collini
 
SISTEMA DE FACTURACION (Ejemplo desarrollado)
SISTEMA DE FACTURACION (Ejemplo desarrollado)SISTEMA DE FACTURACION (Ejemplo desarrollado)
SISTEMA DE FACTURACION (Ejemplo desarrollado)Darwin Durand
 
Better Bullshit Driven Development [SeleniumCamp 2017]
Better Bullshit Driven Development [SeleniumCamp 2017]Better Bullshit Driven Development [SeleniumCamp 2017]
Better Bullshit Driven Development [SeleniumCamp 2017]automician
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patternsSamuel ROZE
 
Dig Deeper into WordPress - WD Meetup Cairo
Dig Deeper into WordPress - WD Meetup CairoDig Deeper into WordPress - WD Meetup Cairo
Dig Deeper into WordPress - WD Meetup CairoMohamed Mosaad
 
Kotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere StockholmKotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere StockholmFabio Collini
 
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf MilanFrom Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf MilanFabio Collini
 
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016Colin O'Dell
 
The Ring programming language version 1.5.4 book - Part 44 of 185
The Ring programming language version 1.5.4 book - Part 44 of 185The Ring programming language version 1.5.4 book - Part 44 of 185
The Ring programming language version 1.5.4 book - Part 44 of 185Mahmoud Samir Fayed
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Railsrstankov
 
Tom Lazar Using Zope3 Views And Viewlets For Plone 3.0 Product Development
Tom Lazar   Using Zope3 Views And Viewlets For Plone 3.0 Product DevelopmentTom Lazar   Using Zope3 Views And Viewlets For Plone 3.0 Product Development
Tom Lazar Using Zope3 Views And Viewlets For Plone 3.0 Product DevelopmentVincenzo Barone
 
How to Mess Up Your Angular UI Components
How to Mess Up Your Angular UI ComponentsHow to Mess Up Your Angular UI Components
How to Mess Up Your Angular UI Componentscagataycivici
 
Backbone js
Backbone jsBackbone js
Backbone jsrstankov
 
Chaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscoreChaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscoreNicolas Carlo
 
The art of reverse engineering flash exploits
The art of reverse engineering flash exploitsThe art of reverse engineering flash exploits
The art of reverse engineering flash exploitsPriyanka Aash
 
Kotlin Delegates in practice - Kotlin community conf
Kotlin Delegates in practice - Kotlin community confKotlin Delegates in practice - Kotlin community conf
Kotlin Delegates in practice - Kotlin community confFabio Collini
 

What's hot (20)

Data20161007
Data20161007Data20161007
Data20161007
 
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night TurinAsync code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form component
 
Architectures in the compose world
Architectures in the compose worldArchitectures in the compose world
Architectures in the compose world
 
SISTEMA DE FACTURACION (Ejemplo desarrollado)
SISTEMA DE FACTURACION (Ejemplo desarrollado)SISTEMA DE FACTURACION (Ejemplo desarrollado)
SISTEMA DE FACTURACION (Ejemplo desarrollado)
 
Better Bullshit Driven Development [SeleniumCamp 2017]
Better Bullshit Driven Development [SeleniumCamp 2017]Better Bullshit Driven Development [SeleniumCamp 2017]
Better Bullshit Driven Development [SeleniumCamp 2017]
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
Dig Deeper into WordPress - WD Meetup Cairo
Dig Deeper into WordPress - WD Meetup CairoDig Deeper into WordPress - WD Meetup Cairo
Dig Deeper into WordPress - WD Meetup Cairo
 
Why ruby
Why rubyWhy ruby
Why ruby
 
Kotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere StockholmKotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere Stockholm
 
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf MilanFrom Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
 
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016
 
The Ring programming language version 1.5.4 book - Part 44 of 185
The Ring programming language version 1.5.4 book - Part 44 of 185The Ring programming language version 1.5.4 book - Part 44 of 185
The Ring programming language version 1.5.4 book - Part 44 of 185
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Rails
 
Tom Lazar Using Zope3 Views And Viewlets For Plone 3.0 Product Development
Tom Lazar   Using Zope3 Views And Viewlets For Plone 3.0 Product DevelopmentTom Lazar   Using Zope3 Views And Viewlets For Plone 3.0 Product Development
Tom Lazar Using Zope3 Views And Viewlets For Plone 3.0 Product Development
 
How to Mess Up Your Angular UI Components
How to Mess Up Your Angular UI ComponentsHow to Mess Up Your Angular UI Components
How to Mess Up Your Angular UI Components
 
Backbone js
Backbone jsBackbone js
Backbone js
 
Chaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscoreChaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscore
 
The art of reverse engineering flash exploits
The art of reverse engineering flash exploitsThe art of reverse engineering flash exploits
The art of reverse engineering flash exploits
 
Kotlin Delegates in practice - Kotlin community conf
Kotlin Delegates in practice - Kotlin community confKotlin Delegates in practice - Kotlin community conf
Kotlin Delegates in practice - Kotlin community conf
 

Similar to FormsKit: reactive forms driven by state. UA Mobile 2017.

20.1 Java working with abstraction
20.1 Java working with abstraction20.1 Java working with abstraction
20.1 Java working with abstractionIntro C# Book
 
준비하세요 Angular js 2.0
준비하세요 Angular js 2.0준비하세요 Angular js 2.0
준비하세요 Angular js 2.0Jeado Ko
 
React new features and intro to Hooks
React new features and intro to HooksReact new features and intro to Hooks
React new features and intro to HooksSoluto
 
Arrays, Structures And Enums
Arrays, Structures And EnumsArrays, Structures And Enums
Arrays, Structures And EnumsBhushan Mulmule
 
RESTful API using scalaz (3)
RESTful API using scalaz (3)RESTful API using scalaz (3)
RESTful API using scalaz (3)Yeshwanth Kumar
 
Resource wrappers in C++
Resource wrappers in C++Resource wrappers in C++
Resource wrappers in C++Ilio Catallo
 
Oops lab manual2
Oops lab manual2Oops lab manual2
Oops lab manual2Mouna Guru
 
PyCon 2010 SQLAlchemy tutorial
PyCon 2010 SQLAlchemy tutorialPyCon 2010 SQLAlchemy tutorial
PyCon 2010 SQLAlchemy tutorialjbellis
 
UIKonf App & Data Driven Design @swift.berlin
UIKonf App & Data Driven Design @swift.berlinUIKonf App & Data Driven Design @swift.berlin
UIKonf App & Data Driven Design @swift.berlinMaxim Zaks
 
The Ring programming language version 1.5 book - Part 8 of 31
The Ring programming language version 1.5 book - Part 8 of 31The Ring programming language version 1.5 book - Part 8 of 31
The Ring programming language version 1.5 book - Part 8 of 31Mahmoud Samir Fayed
 
Migrating Objective-C to Swift
Migrating Objective-C to SwiftMigrating Objective-C to Swift
Migrating Objective-C to SwiftElmar Kretzer
 
Working effectively with ViewModels and TDD - UA Mobile 2019
Working effectively with ViewModels and TDD - UA Mobile 2019Working effectively with ViewModels and TDD - UA Mobile 2019
Working effectively with ViewModels and TDD - UA Mobile 2019UA Mobile
 
Creating a Facebook Clone - Part XXIX - Transcript.pdf
Creating a Facebook Clone - Part XXIX - Transcript.pdfCreating a Facebook Clone - Part XXIX - Transcript.pdf
Creating a Facebook Clone - Part XXIX - Transcript.pdfShaiAlmog1
 
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdfSummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdfARORACOCKERY2111
 
The Ring programming language version 1.7 book - Part 48 of 196
The Ring programming language version 1.7 book - Part 48 of 196The Ring programming language version 1.7 book - Part 48 of 196
The Ring programming language version 1.7 book - Part 48 of 196Mahmoud Samir Fayed
 
Working effectively with legacy code
Working effectively with legacy codeWorking effectively with legacy code
Working effectively with legacy codeShriKant Vashishtha
 

Similar to FormsKit: reactive forms driven by state. UA Mobile 2017. (20)

20.1 Java working with abstraction
20.1 Java working with abstraction20.1 Java working with abstraction
20.1 Java working with abstraction
 
준비하세요 Angular js 2.0
준비하세요 Angular js 2.0준비하세요 Angular js 2.0
준비하세요 Angular js 2.0
 
React new features and intro to Hooks
React new features and intro to HooksReact new features and intro to Hooks
React new features and intro to Hooks
 
Web-First Design Patterns
Web-First Design PatternsWeb-First Design Patterns
Web-First Design Patterns
 
Vue.js slots.pdf
Vue.js slots.pdfVue.js slots.pdf
Vue.js slots.pdf
 
Django quickstart
Django quickstartDjango quickstart
Django quickstart
 
Arrays, Structures And Enums
Arrays, Structures And EnumsArrays, Structures And Enums
Arrays, Structures And Enums
 
RESTful API using scalaz (3)
RESTful API using scalaz (3)RESTful API using scalaz (3)
RESTful API using scalaz (3)
 
Resource wrappers in C++
Resource wrappers in C++Resource wrappers in C++
Resource wrappers in C++
 
Oops lab manual2
Oops lab manual2Oops lab manual2
Oops lab manual2
 
PyCon 2010 SQLAlchemy tutorial
PyCon 2010 SQLAlchemy tutorialPyCon 2010 SQLAlchemy tutorial
PyCon 2010 SQLAlchemy tutorial
 
UIKonf App & Data Driven Design @swift.berlin
UIKonf App & Data Driven Design @swift.berlinUIKonf App & Data Driven Design @swift.berlin
UIKonf App & Data Driven Design @swift.berlin
 
The Ring programming language version 1.5 book - Part 8 of 31
The Ring programming language version 1.5 book - Part 8 of 31The Ring programming language version 1.5 book - Part 8 of 31
The Ring programming language version 1.5 book - Part 8 of 31
 
Migrating Objective-C to Swift
Migrating Objective-C to SwiftMigrating Objective-C to Swift
Migrating Objective-C to Swift
 
Working effectively with ViewModels and TDD - UA Mobile 2019
Working effectively with ViewModels and TDD - UA Mobile 2019Working effectively with ViewModels and TDD - UA Mobile 2019
Working effectively with ViewModels and TDD - UA Mobile 2019
 
Creating a Facebook Clone - Part XXIX - Transcript.pdf
Creating a Facebook Clone - Part XXIX - Transcript.pdfCreating a Facebook Clone - Part XXIX - Transcript.pdf
Creating a Facebook Clone - Part XXIX - Transcript.pdf
 
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdfSummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
 
#JavaFX.forReal() - ElsassJUG
#JavaFX.forReal() - ElsassJUG#JavaFX.forReal() - ElsassJUG
#JavaFX.forReal() - ElsassJUG
 
The Ring programming language version 1.7 book - Part 48 of 196
The Ring programming language version 1.7 book - Part 48 of 196The Ring programming language version 1.7 book - Part 48 of 196
The Ring programming language version 1.7 book - Part 48 of 196
 
Working effectively with legacy code
Working effectively with legacy codeWorking effectively with legacy code
Working effectively with legacy code
 

More from UA Mobile

Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...UA Mobile
 
Декларативное программирование клиент-серверных приложений на андроид - UA Mo...
Декларативное программирование клиент-серверных приложений на андроид - UA Mo...Декларативное программирование клиент-серверных приложений на андроид - UA Mo...
Декларативное программирование клиент-серверных приложений на андроид - UA Mo...UA Mobile
 
Leave your Room behind - UA Mobile 2019
Leave your Room behind - UA Mobile 2019Leave your Room behind - UA Mobile 2019
Leave your Room behind - UA Mobile 2019UA Mobile
 
OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019
OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019
OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019UA Mobile
 
Google Wear OS watch faces and applications development - UA Mobile 2019
Google Wear OS watch faces and applications development - UA Mobile 2019Google Wear OS watch faces and applications development - UA Mobile 2019
Google Wear OS watch faces and applications development - UA Mobile 2019UA Mobile
 
Історія декількох проектів та що в них пішло не так - UA Mobile 2019
Історія декількох проектів та що в них пішло не так - UA Mobile 2019Історія декількох проектів та що в них пішло не так - UA Mobile 2019
Історія декількох проектів та що в них пішло не так - UA Mobile 2019UA Mobile
 
Managing State in Reactive applications - UA Mobile 2019
Managing State in Reactive applications - UA Mobile 2019Managing State in Reactive applications - UA Mobile 2019
Managing State in Reactive applications - UA Mobile 2019UA Mobile
 
Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019
Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019
Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019UA Mobile
 
Актуальні практики дизайну мобільних додатків - UA Mobile 2019
Актуальні практики дизайну мобільних додатків - UA Mobile 2019Актуальні практики дизайну мобільних додатків - UA Mobile 2019
Актуальні практики дизайну мобільних додатків - UA Mobile 2019UA Mobile
 
До чого прикладати Docker в Android? - UA Mobile 2019
До чого прикладати Docker в Android? - UA Mobile 2019До чого прикладати Docker в Android? - UA Mobile 2019
До чого прикладати Docker в Android? - UA Mobile 2019UA Mobile
 
Building your Flutter apps using Redux - UA Mobile 2019
Building your Flutter apps using Redux - UA Mobile 2019Building your Flutter apps using Redux - UA Mobile 2019
Building your Flutter apps using Redux - UA Mobile 2019UA Mobile
 
Optional. Tips and Tricks - UA Mobile 2019
Optional. Tips and Tricks - UA Mobile 2019Optional. Tips and Tricks - UA Mobile 2019
Optional. Tips and Tricks - UA Mobile 2019UA Mobile
 
Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...UA Mobile
 
Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019
Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019
Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019UA Mobile
 
Flutter: No more boring apps! - UA Mobile 2019
Flutter: No more boring apps! - UA Mobile 2019Flutter: No more boring apps! - UA Mobile 2019
Flutter: No more boring apps! - UA Mobile 2019UA Mobile
 
Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019
Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019
Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019UA Mobile
 
Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019
Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019
Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019UA Mobile
 
Sceneform SDK на практиці - UA Mobile 2019
Sceneform SDK на практиці - UA Mobile 2019Sceneform SDK на практиці - UA Mobile 2019
Sceneform SDK на практиці - UA Mobile 2019UA Mobile
 
Coroutines in Kotlin. UA Mobile 2017.
Coroutines in Kotlin. UA Mobile 2017.Coroutines in Kotlin. UA Mobile 2017.
Coroutines in Kotlin. UA Mobile 2017.UA Mobile
 
Augmented reality on Android. UA Mobile 2017.
Augmented reality on Android. UA Mobile 2017.Augmented reality on Android. UA Mobile 2017.
Augmented reality on Android. UA Mobile 2017.UA Mobile
 

More from UA Mobile (20)

Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...
 
Декларативное программирование клиент-серверных приложений на андроид - UA Mo...
Декларативное программирование клиент-серверных приложений на андроид - UA Mo...Декларативное программирование клиент-серверных приложений на андроид - UA Mo...
Декларативное программирование клиент-серверных приложений на андроид - UA Mo...
 
Leave your Room behind - UA Mobile 2019
Leave your Room behind - UA Mobile 2019Leave your Room behind - UA Mobile 2019
Leave your Room behind - UA Mobile 2019
 
OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019
OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019
OpenId and OAuth2: Rear, Medium, Well Done - UA Mobile 2019
 
Google Wear OS watch faces and applications development - UA Mobile 2019
Google Wear OS watch faces and applications development - UA Mobile 2019Google Wear OS watch faces and applications development - UA Mobile 2019
Google Wear OS watch faces and applications development - UA Mobile 2019
 
Історія декількох проектів та що в них пішло не так - UA Mobile 2019
Історія декількох проектів та що в них пішло не так - UA Mobile 2019Історія декількох проектів та що в них пішло не так - UA Mobile 2019
Історія декількох проектів та що в них пішло не так - UA Mobile 2019
 
Managing State in Reactive applications - UA Mobile 2019
Managing State in Reactive applications - UA Mobile 2019Managing State in Reactive applications - UA Mobile 2019
Managing State in Reactive applications - UA Mobile 2019
 
Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019
Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019
Ідіоматична ін'єкція залежностей на Kotlin без фреймворків - UA Mobile2019
 
Актуальні практики дизайну мобільних додатків - UA Mobile 2019
Актуальні практики дизайну мобільних додатків - UA Mobile 2019Актуальні практики дизайну мобільних додатків - UA Mobile 2019
Актуальні практики дизайну мобільних додатків - UA Mobile 2019
 
До чого прикладати Docker в Android? - UA Mobile 2019
До чого прикладати Docker в Android? - UA Mobile 2019До чого прикладати Docker в Android? - UA Mobile 2019
До чого прикладати Docker в Android? - UA Mobile 2019
 
Building your Flutter apps using Redux - UA Mobile 2019
Building your Flutter apps using Redux - UA Mobile 2019Building your Flutter apps using Redux - UA Mobile 2019
Building your Flutter apps using Redux - UA Mobile 2019
 
Optional. Tips and Tricks - UA Mobile 2019
Optional. Tips and Tricks - UA Mobile 2019Optional. Tips and Tricks - UA Mobile 2019
Optional. Tips and Tricks - UA Mobile 2019
 
Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...Designing iOS+Android project without using multiplatform frameworks - UA Mob...
Designing iOS+Android project without using multiplatform frameworks - UA Mob...
 
Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019
Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019
Бібліотеки та Інструменти на сторожі коду - UA Mobile 2019
 
Flutter: No more boring apps! - UA Mobile 2019
Flutter: No more boring apps! - UA Mobile 2019Flutter: No more boring apps! - UA Mobile 2019
Flutter: No more boring apps! - UA Mobile 2019
 
Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019
Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019
Долаючи прірву між дизайнерами та розробниками - UA Mobile 2019
 
Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019
Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019
Multiplatform shared codebase with Kotlin/Native - UA Mobile 2019
 
Sceneform SDK на практиці - UA Mobile 2019
Sceneform SDK на практиці - UA Mobile 2019Sceneform SDK на практиці - UA Mobile 2019
Sceneform SDK на практиці - UA Mobile 2019
 
Coroutines in Kotlin. UA Mobile 2017.
Coroutines in Kotlin. UA Mobile 2017.Coroutines in Kotlin. UA Mobile 2017.
Coroutines in Kotlin. UA Mobile 2017.
 
Augmented reality on Android. UA Mobile 2017.
Augmented reality on Android. UA Mobile 2017.Augmented reality on Android. UA Mobile 2017.
Augmented reality on Android. UA Mobile 2017.
 

FormsKit: reactive forms driven by state. UA Mobile 2017.

  • 3. 3
  • 5. Managing state can bean extremelyhardtask » Mutability » Observation » Thread-safety 5
  • 8. final class Property<Value> { private var handlers: [(Value) -> Void] = [] var value: Value { didSet { handlers.forEach { $0(value) } } } init(value: Value) { self.value = value } func observe(_ handler: @escaping (Value) -> Void) { self.handlers.append(handler) handler(value) } func map<T>(_ transform: @escaping (Value) -> T) -> Property<T> { let property = Property<T>(value: transform(value)) observe { [weak property] value in property?.value = transform(value) } return property } } 8
  • 9. ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠ Please notethis isafairly simple implementation ! ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠ 9
  • 10. Propertygive usanicewayto observe changes 1> let a = Property(value: 1) 2> a.observe { value in print("Value: (value)") } "Value: 1" 3> a.value = a.value + 2 "Value: 3" 10
  • 11. Andto derive newstates 1> let a = Property(value: 1.0) 2> let b = a.map { value in "(value) as String" } 4> b.value "1.0 as String" 5> a.value = a.value * 10 6> a.value 10.0 7> b.value "10.0 as String" 11
  • 13. 13
  • 14. // Sign-Up Table View DataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 12 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { switch indexPath.row { case 0: // Facebook button case 1: // Or text case 2: // First Name case 3: // Last Name ... } } 14
  • 15. Adding or removing elements requires: » update the total number of elements » shift all indices affected Moving elements requires: » shift all indices affected 15
  • 17. 17
  • 18. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { switch indexPath.row { case 0: // Account selected case 1: switch consultantType { case .gp: switch dateAndTimeAvailable { case true: ... case false: ... } case .specialist: ... } case ???: ... ... } } 18
  • 20. 20
  • 21. Index pathsare aweak system, hard to manage and maintain21
  • 26. Aform isacollection of components following asequential order 26
  • 27. 27
  • 28. A form isa collection ofcomponents following a sequentialorder 28
  • 30. ArrayAn ordered, random-access collection 1> let array = [0, 1, 2] 2> array [ 0, 1, 2 ] 30
  • 32. » Text » Text Input » Password » Selection » Buttons » ... 32
  • 33. enum Component { case text(String) case textInput(placeholder: String, ...) case password(placeholder: String, ...) case selection(...) case button(title: String, ...) case facebookButton(...) case toggle(title: String, ...) ... } 33
  • 34. let components: [Component] = [ .facebookButton(...), // Row 0 .text("OR"), // Row 1 .textInput(placeholder: "First name", ...), // Row 2 .textInput(placeholder: "Last name", ...), // Row 3 .textInput(placeholder: "Email", ...), // Row 4 .password(placeholder: "Password", ...), // Row 5 ... ] 34
  • 36. Adding, removing or moving elements is super easy✨ let components: [Component] = [ .textInput(placeholder: "First name", ...), // Row 0 .textInput(placeholder: "Last name", ...), // Row 1 .textInput(placeholder: "Email", ...), // Row 2 .password(placeholder: "Password", ...), // Row 3 ... ] 36
  • 37. Buthow dowe go fromacollection of componentstoa form? ! 37
  • 38. protocol Form { var components: Property<[FormComponent]> { get } } protocol FormComponent { func matches(with component: FormComponent) -> Bool func render() -> UIView & FormComponentView } protocol FormComponentView { ... } enum Component: FormComponent { ... } 38
  • 39. ⚠ Disclaimer ⚠ The following examplesare based on MVVM 39
  • 40. 40
  • 41. Butthis can be appliedtoanything, including MVC ifyou are wondering ! 41
  • 42. class SignUpViewModel: Form { let components: Property<[Component]> init(...) { self.components = Property(value: [ .facebookButton(...), .text("OR"), .textInput(placeholder: "First name", ...), .textInput(placeholder: "Last name", ...), .textInput(placeholder: "Email", ...), .password(placeholder: "Password", ...), ... ]) } } 42
  • 43. final class FormTableViewDataSource: NSObject, UITableViewDataSource { private var currentComponents: [FormComponent] = [] init(tableView: UITableView, components: Property<[FormComponent]>) { components.observe { components in self.currentComponents = components tableView.reloadData() } } } open class FormViewController<F: Form>: UIViewController { private let tableView = UITableView() private let dataSource: FormTableViewDataSource init(form: F) { dataSource = FormTableViewDataSource( tableView: tableView, components: form.components ) ... } } 43
  • 44. final class SignUpViewController: FormViewController { init(viewModel: SignUpViewModel) { super.init(form: viewModel) } } 44
  • 45. Ok butwhatifwe needa dynamic collection of components? ! 45
  • 46. Quick Example: Sign-Inwithtwotypes ofauthentication » Email + Password » Email + Membership ID 46
  • 47. 47
  • 48. enum SignInType { case standard case membership } class SignInViewModel: Form { let components: Property<[Component]> init(...) { self.components = ??? ! } } 48
  • 49. switch signInType { case .standard: components = Property(value: [ .textInput(placeholder: "Email", ...), .password(placeholder: "Password", ...), .toggle(title: "Sign-In with Membership ID", ...) .button(title: "Submit", ...) ]) case .membership: components = Property(value: [ .textInput(placeholder: "Email", ...), .toggle(title: "Sign-In with Membership ID", ...) .textInput(placeholder: "Membership ID", ...), .button(title: "Submit", ...) ]) } 49
  • 50. Firstwe needatriggerto change from one statetothe other .toggle(title: "Sign-In with Membership ID", ...) 50
  • 51. enum Component: FormComponent { ... case toggle(title: String, isOn: Property<Bool>) ... } Nowwe can define component's initialvalue and observeanychanges 51
  • 52. enum SignInType { case standard case membership } class SignInViewModel: Form { private isMembershipSelected = Property(value: false) let components: Property<[Component]> init(...) { self.components = ??? ! } } 52
  • 53. enum SignInType { case standard, membership } class SignInViewModel: Form { private isMembershipSelected = Property(value: false) private signInType: Property<SignInType> let components: Property<[Component]> init(...) { self.signInType = isMembershipSelected.map { isSelected in return isSelected ? .membership : .standard } self.components = ??? ! } } 53
  • 54. enum SignInType { case standard, membership } class SignInViewModel: Form { private isMembershipSelected = Property(value: false) private signInType: Property<SignInType> let components: Property<[Component]> init(...) { self.signInType = ... self.components = signInType.map { signInType in switch signInType { case .standard: ... case .membership: ... } } } } 54
  • 55. self.components = signInType.map { signInType in switch signInType { case .standard: return [ ... .toggle( title: "Sign-In with Membership ID", isOn: isMembershipSelected ) ... ] case .membership: return [ ... .toggle( title: "Sign-In with Membership ID", isOn: isMembershipSelected ) ... ] } } 55
  • 56. 56
  • 57. switch signInType { case .standard: components = Property(value: [ .textInput(placeholder: "Email", ...), ... .toggle(title: "Sign-In with Membership ID", ...) .button(title: "Submit", ...) ]) case .membership: components = Property(value: [ .textInput(placeholder: "Email", ...), .toggle(title: "Sign-In with Membership ID", ...) ... .button(title: "Submit", ...) ]) } 57
  • 58. Weare repeatingthe same components for each state... can'twe compose this inabetterway? 58
  • 61. struct FormBuilder<Component: FormComponent> { let components: [Component] public static var empty: FormBuilder { return FormBuilder() } init(components: [Component] = []) { self.components = components } } 61
  • 62. We havethe container, now we onlyneed methodsto compose 62
  • 64. precedencegroup ChainingPrecedence { associativity: left higherThan: TernaryPrecedence } // Compose with a new component infix operator |-+ : ChainingPrecedence // Compose with another builder infix operator |-* : ChainingPrecedence 64
  • 65. struct FormBuilder { ... static func |-+( builder: FormBuilder, component: Component ) -> FormBuilder { ... } static func |-* ( builder: FormBuilder, components: [Component] ) -> FormBuilder { ... } } 65
  • 66. func signInStandardComponents(for signInType: SignInType) -> FormBuilder { guard let signInType == .standard else { return .empty } return FormBuilder.empty |-+ .password(placeholder: "Password", ...) } func signInMembershipComponents(for signInType: SignInType) -> FormBuilder { guard let signInType == .standard else { return .empty } return FormBuilder.empty |-+ .textInput(placeholder: "Membership ID", ...) } self.components = signInType.map { signInType in let builder = FormBuilder<Component>.empty |-+ .textInput(placeholder: "Email", ...) |-* signInStandardComponents(for: signInType) |-+ .toggle(title: "Sign-In with Membership ID", isOn: isMembershipSelected) |-* signInMembershipComponents(for: signInType) |-+ .button(title: "Submit", ...) return builder.components } 66
  • 68. // Compose components pending on a boolean condition infix operator |-? : ChainingPrecedence struct FormBuilder<Component: FormComponent> { ... static func |-? ( builder: FormBuilder, validator: Validator<FormBuilder<Component>> ) -> FormBuilder { ... } } struct Validator<T> { // `iff` stands for "if and only if" from math and logic static func iff( _ condition: @autoclosure @escaping () -> Bool, generator: @escaping (T) -> T ) -> Validator<T> { ... } } 68
  • 69. self.components = signInType.map { signInType in let builder = FormBuilder<Component>.empty |-+ .textInput(placeholder: "Email", ...) |-? .iff(signInType == .standard) { $0 |-+ .password(placeholder: "Password", ...) } |-+ .toggle(title: "Sign-In with Membership ID", isOn: isMembershipSelected) |-? .iff(signInType == .membership) { $0 |-+ .textInput(placeholder: "Membership ID", ...) } |-+ .button(title: "Submit", ...) return builder.components } 69
  • 70. Nowwe haveadynamic collection of componentsand consequentlya dynamic rendering 70
  • 72. We can definearendererto renderthe collection of components for each state 72
  • 73. protocol Renderer { associatedtype FormState func render(state: FormState) -> [FormComponent] } struct SignInRenderer: Renderer { func render(state: SignInType) -> [FormComponent] { let builder = FormBuilder<Component>.empty |-+ .textInput(placeholder: "Email", ...) |-? .iff(signInType == .standard) { $0 |-+ .password(placeholder: "Password", ...) } |-+ .toggle(title: "Sign-In with Membership ID", isOn: isMembershipSelected) |-? .iff(signInType == .membership) { $0 |-+ .textInput(placeholder: "Membership ID", ...) } |-+ .button(title: "Submit", ...) return builder.components } } 73
  • 74. enum SignInType { case standard, membership } class SignInViewModel: Form { private isMembershipSelected = Property(value: false) private signInType: Property<SignInType> let components: Property<[Component]> init(...) { self.signInType = isMembershipSelected.map { isSelected in return isSelected ? .membership : .standard } let renderer = SignInRenderer( isMembershipSelected: isMembershipSelected, ... ) self.components = signInType.map(renderer.render(state:)) } } 74
  • 76. YES76
  • 77. final class FormTableViewDataSource: NSObject, UITableViewDataSource { private var currentComponents: [FormComponent] = [] init(tableView: UITableView, components: Property<[FormComponent]>) { components.observe { components in self.currentComponents = components tableView.reloadData() // ! } } } 77
  • 79. Everytime our state changes,anewcollection ofcomponents is generatedto reflectthat particular state 79
  • 80. 1> (signInViewModel.signInType, signInViewModel.components) (SignInType.standard, [ .textInput(placeholder: "Email", ...), .password(placeholder: "Password", ...), .toggle(title: "Sign-In with Membership ID", ...), .button(title: "Submit", ...) ]) 2> (signInViewModel.signInType, signInViewModel.components) (SignInType.membership, [ .textInput(placeholder: "Email", ...), .toggle(title: "Sign-In with Membership ID", ...), .textInput(placeholder: "Membership ID", ...), .button(title: "Submit", ...) ]) 80
  • 81. 1> (signInViewModel.signInType, signInViewModel.components) // (SignInType.standard, [ // .textInput(placeholder: "Email", ...), .password(placeholder: "Password", ...), // .toggle(title: "Sign-In with Membership ID", ...), // .button(title: "Submit", ...) //]) 2> (signInViewModel.signInType, signInViewModel.components) // (SignInType.membership, [ // .textInput(placeholder: "Email", ...), // .toggle(title: "Sign-In with Membership ID", ...), .textInput(placeholder: "Membership ID", ...), // .button(title: "Submit", ...) //]) 81
  • 82. SignInType.standard ➡ SignInType.membership » REMOVES (-) .password(placeholder: "Password", ...) » INSERTS (+) .textInput(placeholder: "Membership ID", ...) 82
  • 83. SignInType.membership ➡ SignInType.standard » REMOVES (-) .textInput(placeholder: "Membership ID", ...) » INSERTS (+) .password(placeholder: "Password", ...) 83
  • 84. Rememberthis? protocol FormComponent { func matches(with component: FormComponent) -> Bool } 84
  • 85. Givenacertain component we can match itagainst another componentto validate iftheyare equivalentor not 85
  • 86. 1> let componentA = Component.password(placeholder: "Password", ...) 2> let componentB = Component.password(placeholder: "Password", ...) 3> let componentC = Component.textInput(placeholder: "Membership ID", ...) 4> componentA.matches(with: componentA) true 5> componentA.matches(with: componentB) true 6> componentA.matches(with: componentC) false 86
  • 87. Then byemployingadiffing algorithmwe can identify anychanges betweentwo collections ofcomponents 87
  • 88. import Dwifft static func diff<Value>( _ lhs: [Value], _ rhs: [Value] ) -> [DiffStep<Value>] where Value: Equatable 88
  • 89. import Dwifft static func diff<Value>( _ lhs: [Value], _ rhs: [Value] ) -> [DiffStep<Value>] where Value: Equatable enum DiffStep<Value> { case insert(Int, Value) case delete(Int, Value) } 89
  • 90. import Dwifft static func diff<Value>( _ lhs: [Value], _ rhs: [Value] ) -> [DiffStep<Value>] where Value: Equatable enum DiffStep<Value> { case insert(Int, Value) case delete(Int, Value) } Value: Equatable ! 90
  • 91. struct FormNode: Equatable { let component: FormComponent static func ==(left: FormNode, right: FormNode) -> Bool { return left.component.matches(with: right.component) } } 91
  • 92. struct FormBuilder<Component: FormComponent> { ... func build() -> [FormNode] { return components.map(FormNode.init) } } protocol Form { var nodes: Property<[FormNode]> { get } } 92
  • 93. final class FormTableViewDataSource: NSObject, UITableViewDataSource { private var currentNodes: [FormNode] = [] init(tableView: UITableView, nodes: Property<[FormNode]>) { nodes.observe { nodes in self.currentNodes = nodes tableView.reloadData() } } } 93
  • 94. nodes.observe { nodes in let previousNodes = self.currentNodes self.currentNodes = nodes tableView.beginUpdates() for step in Dwifft.diff(previousNodes, currentNodes) { switch step { case let .insert(index, _): tableView.insertRows( at: [IndexPath(row: index, section: 0)], with: .fade) case let .delete(index, _): tableView.deleteRows( at: [IndexPath(row: index, section: 0)], with: .fade) } } tableView.endUpdates() } 94
  • 96. 96
  • 97. The conceptis highlyinspired on React from Facebook 97
  • 99. Managing state is hard but we cantryto minimise how hard itbecomes 99
  • 100. State derivation is essentialto reduce mutabilityandachievea self-descriptive system 100
  • 101. FormsKit's ultimate goalis to helpcreatingand managinganykind ofform Openforextension 101