Digging In To The Apollo iOS SDK - GraphQL Summit, San Francisco, October 2019

Digging In To The Apollo iOS SDK - GraphQL Summit, San Francisco, October 2019

Learn about the what and why of recent updates to ApolloGraphQL’s Swift iOS SDK, including updates to code generation, get a few concepts on the iOS side more clearly explained, and learn about where we're going with the iOS SDK!

(Will link to video as soon as it's up!)

C4861b1dfdf3bbb21faec4a1acdf183d?s=128

Ellen Shapiro

October 31, 2019
Tweet

Transcript

  1. ⚒ Digging in to the Apollo iOS SDK GraphQL Summit

    | San Francisco, CA | October 2019 by Ellen Shapiro | @DesignatedNerd | apollographql.com
  2. !

  3. !

  4. !

  5. !

  6. !

  7. !

  8. !

  9. ! "

  10. !

  11. !

  12. None
  13. ! ☠

  14. None
  15. None
  16. May 1, 2019

  17. None
  18. !

  19. None
  20. !

  21. None
  22. None
  23. October 30, 2019

  24. What Changed?

  25. Getting Started

  26. Adding an iOS library, normally 1. Install dependency using one

    of: → pod install → carthage checkout → swift build
  27. Adding an iOS library, normally 1. Install dependency using one

    of: → pod install → carthage checkout → swift build 2. Use library.
  28. Code Generation

  29. schema + queries = code

  30. schema + queries + = code

  31. ! Type Safety through the whole stack

  32. Codegen needs to know

  33. Codegen needs to know → Where is the schema file?

  34. Codegen needs to know → Where is the schema file?

    → Where are the query files?
  35. Codegen needs to know → Where is the schema file?

    → Where are the query files? → Where should generated code be output?
  36. Codegen needs to know → Where is the schema file?

    → Where are the query files? → Where should generated code be output? → Any other options which are available from the codegen library
  37. Node + TypeScript

  38. ! " !

  39. Adding apollo-ios before

  40. Adding apollo-ios before 1. Install dependency

  41. Adding apollo-ios before 1. Install dependency 2. Add build script

  42. Adding apollo-ios before 1. Install dependency 2. Add build script

    3. npm install apollo
  43. Adding apollo-ios before 1. Install dependency 2. Add build script

    3. npm install apollo 4. brew install npm
  44. Adding apollo-ios before 1. Install dependency 2. Add build script

    3. npm install apollo 4. brew install npm 5. Google how to install Homebrew
  45. Adding apollo-ios before 1. Install dependency 2. Add build script

    3. npm install apollo 4. brew install npm 5. Google how to install Homebrew 6.
  46. Adding apollo-ios before 1. Install dependency 2. Add build script

    3. npm install apollo
  47. Adding apollo-ios before 1. Install dependency 2. Add build script

    3. npm install apollo 4. Wait, what version of the CLI is this?
  48. Adding apollo-ios before 1. Install dependency 2. Add build script

    3. npm install apollo 4. Wait, what version of the CLI is this? 5. How do I tell what's installed globally vs locally?
  49. Adding apollo-ios before 1. Install dependency 2. Add build script

    3. npm install apollo 4. Wait, what version of the CLI is this? 5. How do I tell what's installed globally vs locally? 6.
  50. !

  51. !

  52. ! ⬇ npm

  53. None
  54. None
  55. None
  56. Adding apollo-ios now

  57. Adding apollo-ios now 1. Install dependency

  58. Adding apollo-ios now 1. Install dependency 2. Add build script

    that calls run-bundled-codegen.sh
  59. Adding apollo-ios now 1. Install dependency 2. Add build script

    that calls run-bundled-codegen.sh 3. Apollo downloads and unzips the correct version of the CLI, all dependencies, and a node runtime.
  60. Adding apollo-ios now 1. Install dependency 2. Add build script

    that calls run-bundled-codegen.sh 3. Apollo downloads and unzips the correct version of the CLI, all dependencies, and a node runtime. 4. Use library.
  61. !

  62. Difficult Concepts need better explanations

  63. Double Optionals??

  64. public struct ReviewInput: GraphQLMapConvertible { public init(stars: Int, commentary: Optional<String?>

    = nil, favoriteColor: Optional<ColorInput?> = nil) { // code } }
  65. Optional<String?>

  66. String??

  67. Optional<Optional<String>>

  68. Optional<Optional<String>> Outer optional: "Is a value even being included for

    this?"
  69. Optional<Optional<String>> Inner optional: "A value was included. Is it nil

    or String?"
  70. None
  71. None
  72. None
  73. None
  74. public struct ReviewInput: GraphQLMapConvertible { public init(stars: Int, commentary: Optional<String?>

    = nil, favoriteColor: Optional<ColorInput?> = nil) { // code } }
  75. None
  76. GraphQL fields are nullable by default

  77. A nullable field can be absent

  78. public struct ReviewInput: GraphQLMapConvertible { public init(stars: Int, commentary: Optional<String?>

    = nil, favoriteColor: Optional<ColorInput?> = nil) { // code } }
  79. ReviewInput(stars: 5) // { "stars": 5 } ReviewInput(stars: 2, commentary:

    "Needs more ewoks") // { "stars": 2, "commentary": "Needs more ewoks" } ReviewInput(stars: 3, commentary: nil) // { "stars": 3 } ReviewInput(stars: 4, commentary: .some(nil)) // { "stars": 4, "commentary": null }
  80. ReviewInput(stars: 5) // { "stars": 5 } ReviewInput(stars: 2, commentary:

    "Needs more ewoks") // { "stars": 2, "commentary": "Needs more ewoks" } ReviewInput(stars: 3, commentary: nil) // { "stars": 3 } ReviewInput(stars: 4, commentary: .some(nil)) // { "stars": 4, "commentary": null }
  81. ReviewInput(stars: 5) // { "stars": 5 } ReviewInput(stars: 2, commentary:

    "Needs more ewoks") // { "stars": 2, "commentary": "Needs more ewoks" } ReviewInput(stars: 3, commentary: nil) // { "stars": 3 } ReviewInput(stars: 4, commentary: .some(nil)) // { "stars": 4, "commentary": null }
  82. ReviewInput(stars: 5) // { "stars": 5 } ReviewInput(stars: 2, commentary:

    "Needs more ewoks") // { "stars": 2, "commentary": "Needs more ewoks" } ReviewInput(stars: 3, commentary: nil) // { "stars": 3 } ReviewInput(stars: 4, commentary: .some(nil)) // { "stars": 4, "commentary": null }
  83. ReviewInput(stars: 5) // { "stars": 5 } ReviewInput(stars: 2, commentary:

    "Needs more ewoks") // { "stars": 2, "commentary": "Needs more ewoks" } ReviewInput(stars: 3, commentary: nil) // { "stars": 3 } ReviewInput(stars: 4, commentary: .some(nil)) // { "stars": 4, "commentary": null }
  84. ReviewInput(stars: 5) // { "stars": 5 } ReviewInput(stars: 2, commentary:

    "Needs more ewoks") // { "stars": 2, "commentary": "Needs more ewoks" } ReviewInput(stars: 3, commentary: nil) // { "stars": 3 } ReviewInput(stars: 4, commentary: .some(nil)) // { "stars": 4, "commentary": null }
  85. Caching

  86. Caching NormalizedCache

  87. With what shall we normalize it?

  88. By default: The path of the query

  89. None
  90. None
  91. None
  92. None
  93. None
  94. None
  95. None
  96. GraphQLID

  97. ISBN

  98. ISBN + UUID

  99. ¯\_(ϑ)_/¯

  100. cacheKeyForObject

  101. cacheKeyForObject (_ object: JSONObject) -> JSONValue?

  102. apollo.cacheKeyForObject = { $0["id"] }

  103. apollo.cacheKeyForObject = { guard let id = $0["id"], let typename

    = $0["__typename"] else { // Not enough info to uniquely identify the object return nil } return [id, typename] }
  104. apollo.cacheKeyForObject = { guard let id = $0["id"], let typename

    = $0["__typename"] else { // Not enough info to uniquely identify the object return nil } return [id, typename] }
  105. apollo.cacheKeyForObject = { guard let id = $0["id"] as? String,

    let typename = $0["__typename"] as? String else { // Not enough info to uniquely identify the object return nil } return id + typename }
  106. apollo.cacheKeyForObject = { guard let isbn = $0["isbn"], let uuid

    = $0["uuid"] else { // Not enough info to uniquely identify the object return nil } return [isbn, uuid] }
  107. In-memory Caching

  108. In-memory Caching InMemoryNormalizedCache

  109. Supplementary libraries

  110. SQLite Caching

  111. SQLite Caching ApolloSQLite

  112. Subscriptions

  113. Episode Stars Commentary Empire ̣̣̣̣̣ This movie rocks! New Hope

    ̣̣̣̣̤ This is the first movie, why is it episode 4?
  114. Episode Stars Commentary Empire ̣̣̣̣̣ This movie rocks! New Hope

    ̣̣̣̣̤ This is the first movie, why is it episode 4? Jedi ̣̣̤̤̤ Needs more ewoks.
  115. Subscriptions ApolloWebSocket

  116. Subscriptions ApolloWebSocket (based on Starscream)

  117. client.subscribe(subscription: ReviewAddedSubscription()) { result in switch result { case .success(let

    graphQLResult): if let review = graphQLResult.data?.reviewAdded { // Handle review being added. } if let errors = graphQLResult.errors { // Handle error from the server } case .failure(let error): // Handle networking error } }
  118. client.subscribe(subscription: ReviewAddedSubscription()) { result in switch result { case .success(let

    graphQLResult): if let review = graphQLResult.data?.reviewAdded { // Handle review being added. } if let errors = graphQLResult.errors { // Handle error from the server } case .failure(let error): // Handle networking error } }
  119. client.subscribe(subscription: ReviewAddedSubscription()) { result in switch result { case .success(let

    graphQLResult): if let review = graphQLResult.data?.reviewAdded { // Handle review being added. } if let errors = graphQLResult.errors { // Handle error from the server } case .failure(let error): // Handle networking error } }
  120. client.subscribe(subscription: ReviewAddedSubscription()) { result in switch result { case .success(let

    graphQLResult): if let review = graphQLResult.data?.reviewAdded { // Handle review being added. } if let errors = graphQLResult.errors { // Handle error from the server } case .failure(let error): // Handle networking error } }
  121. var subscription: Cancellable? self.subscription = client .subscribe(subscription: ReviewAddedSubscription()) { [weak

    self] result in guard let self = self else { return } switch result { case .success(let graphQLResult): if let review = graphQLResult.data?.reviewAdded { // Handle review being added. } if let errors = graphQLResult.errors { // Handle error from the server } case .failure(let error): // Handle networking error } } dealloc { self.subscription?.cancel() }
  122. var subscription: Cancellable? self.subscription = client .subscribe(subscription: ReviewAddedSubscription()) { [weak

    self] result in guard let self = self else { return } switch result { case .success(let graphQLResult): if let review = graphQLResult.data?.reviewAdded { // Handle review being added. } if let errors = graphQLResult.errors { // Handle error from the server } case .failure(let error): // Handle networking error } } dealloc { self.subscription?.cancel() }
  123. var subscription: Cancellable? self.subscription = client .subscribe(subscription: ReviewAddedSubscription()) { [weak

    self] result in guard let self = self else { return } switch result { case .success(let graphQLResult): if let review = graphQLResult.data?.reviewAdded { // Handle review being added. } if let errors = graphQLResult.errors { // Handle error from the server } case .failure(let error): // Handle networking error } } dealloc { self.subscription?.cancel() }
  124. var subscription: Cancellable? self.subscription = client .subscribe(subscription: ReviewAddedSubscription()) { [weak

    self] result in guard let self = self else { return } switch result { case .success(let graphQLResult): if let review = graphQLResult.data?.reviewAdded { // Handle review being added. } if let errors = graphQLResult.errors { // Handle error from the server } case .failure(let error): // Handle networking error } } dealloc { self.subscription?.cancel() }
  125. var subscription: Cancellable? self.subscription = client .subscribe(subscription: ReviewAddedSubscription()) { [weak

    self] result in guard let self = self else { return } switch result { case .success(let graphQLResult): if let review = graphQLResult.data?.reviewAdded { // Handle review being added. } if let errors = graphQLResult.errors { // Handle error from the server } case .failure(let error): // Handle networking error } } dealloc { self.subscription?.cancel() }
  126. None
  127. ! The Future

  128. None
  129. None
  130. ! Free-form responses

  131. → "Poor tutorial support"

  132. → "Poor tutorial support" → "Better documentation."

  133. → "Poor tutorial support" → "Better documentation." → "More documentation

    on uploading techniques, caching, and background communication."
  134. → "Poor tutorial support" → "Better documentation." → "More documentation

    on uploading techniques, caching, and background communication." → "Expand documentation on SQLiteNormalizedCache"
  135. Better Documentation

  136. More Comprehensive Tutorial

  137. I have IDEAS!

  138. I have IDEAS! ...Are these good ideas?

  139. I have IDEAS! ...Are these good ideas?

  140. Use Codable instead of custom parsing to the extent that

    this is possible
  141. Use Codable instead of custom parsing

  142. Contributing to codegen when it's written in Typescript is hard

  143. Swift Codegen

  144. Swift Codegen (with parsing and validation still via TypeScript, probably)

  145. Generating Swift Code in Swift

  146. As part of Swift Codegen:

  147. As part of Swift Codegen: → Codable conformance

  148. As part of Swift Codegen: → Codable conformance → Equatable/Hashable

    conformance
  149. As part of Swift Codegen: → Codable conformance → Equatable/Hashable

    conformance → xcfilelists of inputs and outputs
  150. As part of Swift Codegen: → Codable conformance → Equatable/Hashable

    conformance → xcfilelists of inputs and outputs → Fragments as protocols
  151. As part of Swift Codegen: → Codable conformance → Equatable/Hashable

    conformance → xcfilelists of inputs and outputs → Fragments as protocols → Get rid of double-optionals for clearer type*
  152. As part of Swift Codegen: → Codable conformance → Equatable/Hashable

    conformance → xcfilelists of inputs and outputs → Fragments as protocols → Get rid of double-optionals for clearer type* → Identifiable auto-conformance*
  153. As part of Swift Codegen: → Codable conformance → Equatable/Hashable

    conformance → xcfilelists of inputs and outputs → Fragments as protocols → Get rid of double-optionals for clearer type* → Identifiable auto-conformance* * - I hope
  154. None
  155. "Version x.y.z breaks on ________"

  156. Dependency Manager Test Suite

  157. "Version x.y.z breaks on NOTHING"

  158. Better Caching

  159. Moar ! Libraries!

  160. Conucrrency is hard

  161. Concurrency is hard

  162. Wrappers for RxSwift, Combine, and PromiseKit

  163. LIST SUBJECT TO CHANGE Combine, PromiseKit,

  164. !

  165. Stuff you contribute!

  166. Stuff you contribute! (seriously though, I would love your help)

  167. None
  168. Obligatory Summary Slide

  169. Obligatory Summary Slide → Check out updated build setup, it's

    way easier
  170. Obligatory Summary Slide → Check out updated build setup, it's

    way easier → Docs have been significantly improved
  171. Obligatory Summary Slide → Check out updated build setup, it's

    way easier → Docs have been significantly improved → New sample app and tutorial coming soon!
  172. Obligatory Summary Slide → Check out updated build setup, it's

    way easier → Docs have been significantly improved → New sample app and tutorial coming soon! → Swift Codegen is coming
  173. Obligatory Summary Slide → Check out updated build setup, it's

    way easier → Docs have been significantly improved → New sample app and tutorial coming soon! → Swift Codegen is coming → Check out the ROADMAP.md
  174. Obligatory Summary Slide → Check out updated build setup, it's

    way easier → Docs have been significantly improved → New sample app and tutorial coming soon! → Swift Codegen is coming → Check out the ROADMAP.md → Contribute!
  175. ! Thank you!

  176. Links! → Main Repo: https://github.com/apollographql/apollo-ios → Prettily rendered documentation: https://apollographql.com/docs/ios/