Comprehensible Code Base - Less Code - Test Coverage - Best Prac9ces for Faster Onboarding Performance - Provide Fast Defaults (Infrastructure) - Make Bad Choices Hard iOS Rewrite | @benjaminencz | PlanGrid, April 2016 3
here • Defines State for a scene • Updates State when Actions come in • Exposes Signal for UI layer to observe state • Communicates with ObjectHaven to read & write data iOS Rewrite | @benjaminencz | PlanGrid, April 2016 13
sorted list of collaborators and admins on the project public var users: [TeamMember] public var selectedUsers: [TeamMember] public var selectionEnabled: Bool = false public var multiselectEnabled: Bool = false public var invitationsEnabled: Bool = false } iOS Rewrite | @benjaminencz | PlanGrid, April 2016 14
store to filter users based on provided search term struct SearchTeamMembers: ProjectSpecificAction { let projectUid: String let searchTerm: String let cursorPosition: Int } /// Action that asks the store to invite a team member struct InviteTeamMember: ProjectSpecificAction { let projectUid: String let userEmail: String } } iOS Rewrite | @benjaminencz | PlanGrid, April 2016 15
button is enabled when the state allows purging self.navigationItem.rightBarButtonItem!.racEnabled <~ self.store.state .map { $0.isPurgable } iOS Rewrite | @benjaminencz | PlanGrid, April 2016 18
• All state updates need to be incremental • This is the root cause of much horrible UI code; all state lives in UIViewController, all state is implicitly part of the UI components itself • ReactiveCocoa is our bridge from the ideal world to the real world iOS Rewrite | @benjaminencz | PlanGrid, April 2016 21
Store updates State when Actions come in • Store exposes Signal for UI layer to observe state • Store communicates with ObjectHaven to read & write data iOS Rewrite | @benjaminencz | PlanGrid, April 2016 24
it into UIKit components / proper9es • Simpler bindings are implemented manually; complex bindings are done via components (e.g. FluxTableViewModel) // The edit button is enabled when the state allows purging self.navigationItem.rightBarButtonItem!.racEnabled <~ self.viewModel .editingEnabled iOS Rewrite | @benjaminencz | PlanGrid, April 2016 30
redundant • Wanted to share persistence code among all types • Provide hooks for customiza(on • Now types only need to adopt ObjectHavenElement protocol iOS Rewrite | @benjaminencz | PlanGrid, April 2016 32
static let name = "report_templates" public static let primaryKey = PrimaryKey(Columns.uid) public struct Columns { public static let uid = Column<String>("uid") public static let reportType = Column<String>("report_type") public static let filename = Column<String>("filename") public static let size = Column<Int>("size") public static let urlFull = OptionalColumn<NSURL>("url_full") public static let urlThumbnail = OptionalColumn<NSURL>("url_thumbnail") } } iOS Rewrite | @benjaminencz | PlanGrid, April 2016 34
persistence features are available to that type: • Saving, dele;ng, querying, reference coun;ng, asset management, change tracking, etc. • Bonus: Free Test Coverage through our Generic Test Suite describe("Test Object Havens") { testHaven(FieldReportTemplateTestCase.self) testHaven(FieldReportTypeTestCase.self) testHaven(FieldReportFileTestCase.self) testHaven(FieldReportTestCase.self) } iOS Rewrite | @benjaminencz | PlanGrid, April 2016 36
saved `SyncRequestState` in the database. static var requestStateKey: String { get } /// Defines API endpoint from which to sync this entity static func apiEndpoint() -> NSURLComponents /// Creates the first in a potential chain of paged requests given the previously saved parameters. /// - note: For requests like `ProjectDigest` that don't use paging parameters will be `nil`. static func startingRequest(state: SyncRequestState?) -> SyncRequest? /// Process the server's response to a request. /// - returns: The list of changes and optionally a request if there is another paginated request. /// The parameters on the returned request will be saved as the "previous parameters". static func processServerResponse(response: SyncResponse, request: SyncRequest, state: SyncRequestState?) -> (changes: [ModelChange], newState: SyncRequestState?, nextRequest: SyncRequest?) } iOS Rewrite | @benjaminencz | PlanGrid, April 2016 42
following for free: • Network Requests for described endpoints • Pagina8on • Persistence of request state in DB (for next sync) • Persistence of downloaded data in ObjectHaven or other PersistenceProvider iOS Rewrite | @benjaminencz | PlanGrid, April 2016 43
modifica3on of local data to reduce redundant code, reduce room for error and enable new sync features, e.g. rollback of changes iOS Rewrite | @benjaminencz | PlanGrid, April 2016 47
mutated directly anymore instead changes are modelled as MutationOperation • MutationOperation describes a change and provides relevant data for push request • CreationOperation and DeletionOperation are created implicitly by ObjectHaven iOS Rewrite | @benjaminencz | PlanGrid, April 2016 48
by default rollbacks will be implemented by common infrastructure • Note: v1 will only do complete rollback of an object (a@er first failed opera3on). v2 will support demonstrated par3al rollback. iOS Rewrite | @benjaminencz | PlanGrid, April 2016 55
the ObjectHaven saves an entity via `saveFromServer`") { beforeEach { OperationDeserializerRegistry.operationTypes.append(PGExampleObjectHavenModelChangeType.self) haven = ObjectHaven() try! haven.database.context.executeUpdate(PGExampleObjectHavenModel.createTable()) operationDAO = SyncOperationDAO(database: haven.database) exampleModel = PGExampleObjectHavenModel() try! haven.saveFromServer(exampleModel) } it("stores a JSON blob that represents the object in the database") { let serverVersion = try haven.latestServerVersion(exampleModel.uid) expect(serverVersion).to(equal(exampleModel)) } context("when a deletion operation is performed") { beforeEach { try! haven.removeLocally(exampleModel) } it("removes the object from the local db") { let object = haven[exampleModel.uid] expect(object).to(beNil()) } } } iOS Rewrite | @benjaminencz | PlanGrid, April 2016 57