Slide 1

Slide 1 text

Data driven view controllers by @DAlooG // 2017 @ GDG Dev Fest

Slide 2

Slide 2 text

Inspired by React.js by @DAlooG // 2017 @ GDG Dev Fest

Slide 3

Slide 3 text

What makes good view controller? → predictable and stable behavior → easy to test and reproduce issues → minimum amount of business code → sync interface by @DAlooG // 2017 @ GDG Dev Fest

Slide 4

Slide 4 text

What makes good abstractions? → fully incapsulated context → operation order agnostic → decoupled from rest of the system by @DAlooG // 2017 @ GDG Dev Fest

Slide 5

Slide 5 text

Usual view controller class ViewController: UIViewController { var database = DatabaseWrapper.shared var network = NetworkAPI.shared ... } by @DAlooG // 2017 @ GDG Dev Fest

Slide 6

Slide 6 text

What is wrong with this? → user action are not visible, nobody can react. → change in core components can break everything. → zero reuse between parts/projects. by @DAlooG // 2017 @ GDG Dev Fest

Slide 7

Slide 7 text

View controller with Props class ViewController: UIViewController { struct Props { ... } func render(props: Props) { ... } } by @DAlooG // 2017 @ GDG Dev Fest

Slide 8

Slide 8 text

What is good with this? → code locality is great → change in app is not reflected in VC → all inputs and outputs is visible by @DAlooG // 2017 @ GDG Dev Fest

Slide 9

Slide 9 text

Can we generalize this? protocol DataDriven { associatedtype Props func render(props: Props) } by @DAlooG // 2017 @ GDG Dev Fest

Slide 10

Slide 10 text

Should we? Probably, no. Exact shape is not so relevant. Ideas matter more that implementation details by @DAlooG // 2017 @ GDG Dev Fest

Slide 11

Slide 11 text

How we can handle view actions? → action closure → Command pattern → dispatch closure by @DAlooG // 2017 @ GDG Dev Fest

Slide 12

Slide 12 text

Action closure Simplest possible idea struct Props { struct Item { ... } let data: [Item] let reload: () -> () let select: (Item) -> () } by @DAlooG // 2017 @ GDG Dev Fest

Slide 13

Slide 13 text

Command pattern struct Command { init(file: StaticString = #file, line: Int = #line, action: (T) -> ()) { ... } func perform(with value: T) { action(value) } } by @DAlooG // 2017 @ GDG Dev Fest

Slide 14

Slide 14 text

Command usage struct Props { struct Item { ... } let data: [Item] let reload: Command let select: Command } by @DAlooG // 2017 @ GDG Dev Fest

Slide 15

Slide 15 text

Dispatch closure class ViewController: UIViewController { struct Props { ... } enum Action { case reload, select(Item) } var props: Props var dispatch: (Action) -> () } by @DAlooG // 2017 @ GDG Dev Fest

Slide 16

Slide 16 text

How to design nice Props? → eliminate impossible states with types → avoid any sort of identifiers → use flatten props by @DAlooG // 2017 @ GDG Dev Fest

Slide 17

Slide 17 text

How to eliminate impossible states? struct Props { let isLoading: Bool let login: Command? let cancel Command? let setUsername: Command? let setPassword: Command? } by @DAlooG // 2017 @ GDG Dev Fest

Slide 18

Slide 18 text

How to eliminate impossible states? enum Props { case input(setUsername: Command, setPassword: Command, login: Command) case loading(cancel: Command) } by @DAlooG // 2017 @ GDG Dev Fest

Slide 19

Slide 19 text

How to avoid identifiers? struct Props { struct Item { let name: String let id: String } let data: [Item] let select: Command by @DAlooG // 2017 @ GDG Dev Fest

Slide 20

Slide 20 text

How to avoid identifiers? struct Props { struct Item { let name: String let select: Command } let data: [Item] } by @DAlooG // 2017 @ GDG Dev Fest

Slide 21

Slide 21 text

How to render complex props? → use enums for modeling value exclusivity → use nested types → use phantom types by @DAlooG // 2017 @ GDG Dev Fest

Slide 22

Slide 22 text

enum Props { case input(Input); struct Input { let username, password: Field; struct Field { let value: String let update: Command } let status: Status; enum Status { case invalid, valid(login: Command) } } case loading(cancel: Command) case cancelling } by @DAlooG // 2017 @ GDG Dev Fest

Slide 23

Slide 23 text

enum UIKitProps { let loginVisible: Bool let loginText: String let loginUpdate: Command? ... init(from props: Props) { ... } } by @DAlooG // 2017 @ GDG Dev Fest

Slide 24

Slide 24 text

class ViewController: UIViewController { var props: Props { didSet { uiProps = UIKitProps(from: props) guard isViewLoaded else { return } view.setNeedsLayout() }} by @DAlooG // 2017 @ GDG Dev Fest

Slide 25

Slide 25 text

class ViewController: UIViewController { override viewWillLayoutSubviews() { login.isHidden = uiProps.loginIsHidden ... } @IBAction func login() { uiProps.loginAction?.perform() } } by @DAlooG // 2017 @ GDG Dev Fest

Slide 26

Slide 26 text

Testing by @DAlooG // 2017 @ GDG Dev Fest

Slide 27

Slide 27 text

How data-driven controllers can be tested? → snapshot tests → interactive storybooks → record/replay by @DAlooG // 2017 @ GDG Dev Fest

Slide 28

Slide 28 text

How to setup a snapshot tests? class ViewControllerTests: FBSnapshotTestCase { func testLoading() { ... } } by @DAlooG // 2017 @ GDG Dev Fest

Slide 29

Slide 29 text

let vc = ViewController() let window = SnapshotWindow.iPhoneX.landscape vc.props = .loading window.rootViewController = vc window.makeKeyAndVisible() FBSnapshotVerifyView(window) by @DAlooG // 2017 @ GDG Dev Fest

Slide 30

Slide 30 text

Pros of snapshot testing → strong regression tracking → easy to review UI related Pull Requests → UI can be fully developed without rest of an app by @DAlooG // 2017 @ GDG Dev Fest

Slide 31

Slide 31 text

Cons of snapshot testing → a lot of images → single bug can break half of images → bad noise/signal ratio → code required for change by @DAlooG // 2017 @ GDG Dev Fest

Slide 32

Slide 32 text

How to setup interactive storybook? → develop UI in a separate framework → run playground → setup props → use LiveView API by @DAlooG // 2017 @ GDG Dev Fest

Slide 33

Slide 33 text

//UI.playground import PlaygroundSupport @testable import AppUI let vc = ViewController() let window = SnapshotWindow.iPhoneX.landscape vc.props = .loading window.rootViewController = vc window.makeKeyAndVisible() PlaygroundPage.current.liveView = window by @DAlooG // 2017 @ GDG Dev Fest

Slide 34

Slide 34 text

Interactive storybook 2.0 → web based UI for editing props → Props should be Codable → WebSockets for syncing between client and host. by @DAlooG // 2017 @ GDG Dev Fest

Slide 35

Slide 35 text

by @DAlooG // 2017 @ GDG Dev Fest

Slide 36

Slide 36 text

How to record and replay props? → attach Recorder to view controller. → save history by some trigger → send it back to developers → replay by @DAlooG // 2017 @ GDG Dev Fest

Slide 37

Slide 37 text

struct Recorder: Codable { struct Event: Codable { let date: Date, let props: Props } var history: [Event] func record(props: Props, date: Date = Date()) { history.append(Event(date: date, props: props)) } func replay(with render: @escaping (Props) -> ()) { ... } by @DAlooG // 2017 @ GDG Dev Fest

Slide 38

Slide 38 text

Additional Unit testing → converting from Props to UIKitProps → invariants of Props by @DAlooG // 2017 @ GDG Dev Fest

Slide 39

Slide 39 text

How to connect one view controller with another? → using storyboard (UI driven) → using some coordinator, pattern (Context driven) by @DAlooG // 2017 @ GDG Dev Fest

Slide 40

Slide 40 text

Storyboard driven transition struct Props { let data: [Item]; struct Item { let name: String let select: Command } } by @DAlooG // 2017 @ GDG Dev Fest

Slide 41

Slide 41 text

When we should use ui driven transition? → several view controllers forms single use case. → we like to keep UI code in the UI :) by @DAlooG // 2017 @ GDG Dev Fest

Slide 42

Slide 42 text

Coordinator driven transition struct Props { let data: [Item]; struct Item { let name: String let select: Command } } by @DAlooG // 2017 @ GDG Dev Fest

Slide 43

Slide 43 text

What about composition of view controllers? class MasterViewController: UIViewController { struct Props { let itemListProps: ItemListViewController.Props let selectedItemProps: DetailsViewController.Props? } } by @DAlooG // 2017 @ GDG Dev Fest

Slide 44

Slide 44 text

What about animations? class ViewController: UIViewController { func render(props: Props, from oldProps: Props, animated: Bool) { ... } } by @DAlooG // 2017 @ GDG Dev Fest

Slide 45

Slide 45 text

Some hints for animations: → use new property animator API. → use view controller transitioning delegates by @DAlooG // 2017 @ GDG Dev Fest

Slide 46

Slide 46 text

Where I can learn more? → our production code: https://github.com/aol- public/OneMobileSDK-controls-ios → my mail: [email protected] → my twitter: @DAlooG → ask questions now by @DAlooG // 2017 @ GDG Dev Fest