Slide 1

Slide 1 text

2 YEARS WITH REDUX LESSONS LEARNED ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 2

Slide 2 text

ALEXEY DEMEDECKIY @DALOOG > 7 years of iOS development > Early adopter > Strong typed person > Semantic oriented programmer

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

DATA-DRIVEN ARCHITECTURES > Flux > Redux > Elm > MobX > Cycle.js ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 6

Slide 6 text

WHY ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 7

Slide 7 text

> Single source of truth > Single place of change > Single way to update ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 8

Slide 8 text

WTF IS GOING ON? ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 9

Slide 9 text

...debugging is twice as hard as writing a program in the first place. — Brian Kernighan (1985) ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 10

Slide 10 text

LOW COST OF CHANGE ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 11

Slide 11 text

LESSONS: 1. Data-driven VC 2. Correct ViewModels 3. Data-driven vs event-driven 4. Managed side effects 5. High level testing ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 12

Slide 12 text

TRADITIONAL MVC class ViewController: UIViewController { var modelID: String? var api: APIClient? var database: DatabaseWrapper? } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 13

Slide 13 text

TRADITIONAL MVC > intermediate state > hard to track changes > fragile UI ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 14

Slide 14 text

REACTIVE MVVM class ViewModel { var name: Observable var age: Observable func birthday() {} func mariage() {} } class ViewController: UIViewController { var viewModel: Observable } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 15

Slide 15 text

REACTIVE MVVM > transient states > sharing view models > reactive paradigm ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

DATA-DRIVEN VC class ViewController: UIViewController { struct ViewModel { let name: String let age: String let birthday: () -> () let marry: () -> () } var viewModel: ViewModel { didSet { setNeedsLayout() } } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 18

Slide 18 text

Lesson #1: EVERY VIEW CONTROLLER SHOULD BE DATA-DRIVEN ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 19

Slide 19 text

LOGGING IN struct ViewModel { let isLoggedIn: Bool let isLoading: Bool let login: (Username, Password) -> () let logout: () -> () } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 20

Slide 20 text

LOGGING IN enum ViewModel { case loginForm(login: (Username, Password) -> ()) case loading case loggedIn(logout: () -> ()) } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 21

Slide 21 text

MAKING IMPOSSIBLE STATES IMPOSSIBLE ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 22

Slide 22 text

REAL LIFE EXAMPLE public enum Props { case noPlayer, player(Player), pictureInPicture public struct Player { public var playlist: Playlist? public struct Playlist { public var next: Action? public var prev: Action? } public var item = Item.nonplayable("") public enum Item { case playable(Controls) case nonplayable(String) } } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 23

Slide 23 text

CORRECT AND EXPECTABLE VS EASY TO RENDER ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 24

Slide 24 text

LOGGING IN struct UIProps { let loginButtonAction: () -> () let loginButtonEnabled: Bool let spinnerHidden: Bool let logoutButtonAction: () -> () let logoutButtonEnabled: Bool init(viewModel: ViewModel) { ... } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 25

Slide 25 text

REAL LIFE EXAMPLE struct UIProps { var loading: Bool ... init(props: Props) { loading = { guard case .player(let player) = props else { return false } guard case .playable(let props) = player.item else { return false } return props.loading }() } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 26

Slide 26 text

BEST OF BOTH WORLDS > Correctness of state > Simplicity of rendering > Tesatbility ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 27

Slide 27 text

Lesson #2: CORRECTNESS OF VALUES SHOULD BE ENFORCED WITH TYPES ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 28

Slide 28 text

DATA / EVENT DUALITY ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 29

Slide 29 text

EVENTS ARE FRAGILE > Order is crucial > Context is important > Errors are cumulative ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 30

Slide 30 text

DATA IS BETTER THAN EVENT > No order constraints > You can miss some data > Eventually ok ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 31

Slide 31 text

REAL LIVE EXAMPLE public struct AVPlayerProps { public let url: URL public let isPlaying: Bool public let isMuted: Bool public let newTime: CMTime? public let callbacks: PlayerObserver.Callbacks public let didPlayToTime: Action } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 32

Slide 32 text

EVENT SOURCES > external systems > side effects > user input ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 33

Slide 33 text

DATA SOURCE ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 34

Slide 34 text

DATA SOURCE class Application { var model: Observable init(from model: Model) { ... } func apply(event: Event) { ... } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 35

Slide 35 text

Lesson #3: SINGLE DATA SHOULD BE ENOUGH ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 36

Slide 36 text

SINGLETONS class APIClient { static let shared = APIClient() private init() { ... } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 37

Slide 37 text

SINGLETONS > Unclear usage > Shared state > Miss of context ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 38

Slide 38 text

DI protocol APIClient { ... } class Service { var apiClient: APIClient } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 39

Slide 39 text

DI > Hard to manage > Transient dependencies > API breakage ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 40

Slide 40 text

DI func login(username: Username, password: Password) { self.apiClient.get( path: "/session", params: ["useraname": useraname, "password": password]) { error, value in if let error = error { return self.showAlert(error) } return self.pushUserPage(value!) } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 41

Slide 41 text

EFFECTS enum Effect { case none case get(Path, Params, (Error?, Value?) -> Effect) case showAlert(Error) case pushUserPage(Value) } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 42

Slide 42 text

EFFECTS func login(username: Username, password: Password) -> Effect { let params = ["username": username, "password": password] return .get("/session", params) { error, value in if let error = error { return .showError(error) } if let value = value { return .pushUserPage(value) } return .none } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 43

Slide 43 text

EFFECTS class Application { var model: Observable init(from model: Model) { ... } func apply(event: Event) { ... } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 44

Slide 44 text

EFFECTS class Application { ... func perform(effect: Effect) { ... } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 45

Slide 45 text

Lesson #4: SEPARATE HOW? AND WHAT? ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 46

Slide 46 text

UNIT TESTS IN DATA-DRIVEN > Easy to start > Low support cost > Show nothing ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 47

Slide 47 text

USUAL INTEGRATION TESTS > Hard to write > Requires constant attention > Covers only critical sections ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 48

Slide 48 text

DATA-DRIVEN INTEGRATION TESTS class Application { var tracer: Tracer var model: Model { didSet { tracer.record(model: model) } } func apply(event: Event) { tracer.record(event) } func perform(effect: Effect) { tracer.record(effect) } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 49

Slide 49 text

RESULTS > events.json - 3MB > model.json - 35MB > effects.json - 4MB ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 50

Slide 50 text

VERIFICATION TESTING > Record real usage scenarios > Verify consistency > Discover side effects ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 51

Slide 51 text

Lesson #5: UNIT TESTS ARE NOT ENOUGH ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 52

Slide 52 text

1. Data-driven VC 2. Correct ViewModels 3. Data-driven vs event-driven 4. Managed side effects 5. High level testing ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 53

Slide 53 text

USEFUL LINKS > Idiomatic Redux > Video Player Controls > Video Renderer > Elm ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 54

Slide 54 text

COMMUNITY EFFORTS > Swift Frameworks by @inamiy > Statements, messages and reducers by @cocoawithlove > Reducers by @chriseidhof ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 55

Slide 55 text

KATANA > Virtual UI > Data-driven layout > Strong foundation ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 56

Slide 56 text

PORTAL BY @GUIDOMB > Elm inspired > Managed side effects > Data-driven layout ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 57

Slide 57 text

QUESTIONS? @DALOOG [email protected] ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF

Slide 58

Slide 58 text

ADVANCED TOPICS > Cross-platform core (JSCore) > Tooling (Redux Dev Tools) > Adopting in teams ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF