Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Offline and Reactive apps with Apollo Kotlin

Offline and Reactive apps with Apollo Kotlin

mbonnin

June 11, 2022
Tweet

More Decks by mbonnin

Other Decks in Programming

Transcript

  1. What is GraphQL? • Schema ◦ Int, Float, String, Boolean

    ◦ Objects, Interfaces ◦ Lists ◦ Nullability • Introspection • Deprecation (and experimental soon 🧪)
  2. How does it look in practice query UserQuery { user

    { id login avatar { small medium } } }
  3. How does it look in practice query UserQuery { user

    { id login name } } { "data": { "user": { "id": "42", "login": "BoD", "name": "Benoit Lubek" } } }
  4. How does it look in practice query UserQuery { user

    { id login name } } { "data": { "user": { "id": "42", "login": "BoD", "name": "Benoit Lubek" } } }
  5. How does it look in practice query UserQuery { user

    { id email login name } } { "data": { "user": { "id": "42", "email": "BoD@JRAF.org", "login": "BoD", "name": "Benoit Lubek" } } }
  6. That doesn’t work with partial entities query ViewerQuery { viewer

    { # Returns a User id email avatarUrl } } query UserQuery($id: String) { user(id: $id) { # Also a User id email login name } }
  7. We could cache the HTTP response { "data": { "viewer":

    { "id": "42", "email": "BoD@JRAF.org", "avatarUrl": "http://…" } } } { "data": { "user": { "id": "42", "email": "BoD@JRAF.org", "login": "BoD", "name": "Benoit Lubek" } } }
  8. Cache normalization - Response { "data": { "user": { "id":

    "42", "email": "BoD@JRAF.org", "login": "BoD", "name": "Benoit Lubek" } } }
  9. Cache normalization - Records { "data": { "user": CacheReference("42"), },

    "42": { "id": "42", "email": "BoD@JRAF.org", "login": "BoD", "name": "Benoit Lubek" } }
  10. Adding fields { "data": { "user": CacheReference("42"), }, "42": {

    "id": "42", "email": "BoD@JRAF.org", "login": "BoD", "name": "Benoit Lubek", // New Record field "avatarUrl": "http://…" } }
  11. { "data": { "user": CacheReference("42"), }, "42": { "id": "42",

    "email": "BoD@JRAF.org", "login": "BoD", "name": "Benoit Lubek", // New Record field "avatarUrl": "http://…" } } Cache ids Ids!
  12. What if there’s no id? { "data": { "user": {

    "email": "BoD@JRAF.org", "login": "BoD", "name": "Benoit Lubek" } } }
  13. The field path is used as id { "data": {

    "user": CacheReference("data.user"), }, "data.user": { "email": "BoD@JRAF.org", "login": "BoD", "name": "Benoit Lubek" } }
  14. What if there are several paths? { "data": { "todo":

    [ { "title": "Write retrowave slides!", "checked": true, "user": { "login": "BoD", "avatarUrl": "https://" }, }, ], } }
  15. The field path is used as id { "data": {

    "user": CacheReference("data.user"), }, "data.user": { "email": "BoD@JRAF.org", "login": "BoD", "name": "Benoit Lubek" }, }
  16. The field path is used as id { "data": {

    "user": CacheReference("data.user"), "todo": CacheReference("data.todo"), }, "data.user": { "email": "BoD@JRAF.org", "login": "BoD", "name": "Benoit Lubek" }, "data.todo": { "title": "Write retrowave slides!", "checked": true, "user": CacheReference("data.todo.user") }, "data.todo[0].user": { "login": "Bod", "avatarUrl": "https///" } }
  17. The field path is used as id { "data": {

    "user": CacheReference("data.user"), "todo": CacheReference("data.todo"), }, "data.user": { "email": "BoD@JRAF.org", "login": "BoD", "name": "Benoit Lubek" }, "data.todo": { "title": "Write retrowave slides!", "checked": true, "user": CacheReference("data.todo.user") }, "data.todo.user": { "login": "Bod", "avatarUrl": "https///" } } Duplication
  18. This is all typesafe { "data": { "user": CacheReference("42"), },

    "42": { "id": "42", "email": "BoD@JRAF.org", "login": "BoD", "name": "Benoit Lubek" } } Data( user=User( id=42, email=BoD@JRAF.org, login=BoD, name=Benoit Lubek ) )
  19. Storage: in-memory or persistent val memoryCache = MemoryCacheFactory(maxSizeBytes = 5_000_000)

    val apolloClient: ApolloClient = ApolloClient.Builder() .serverUrl(SERVER_URL) .normalizedCache(memoryCache) .build()
  20. Storage: in-memory or persistent val sqlCache = SqlNormalizedCacheFactory(context, "app.db") val

    apolloClient: ApolloClient = ApolloClient.Builder() .serverUrl(SERVER_URL) .normalizedCache(sqlCache) .build()
  21. Storage: in-memory and persistent val memoryCache = MemoryCacheFactory(maxSizeBytes = 5_000_000)

    val sqlCache = SqlNormalizedCacheFactory(context, "app.db") val memoryThenSqlCache = memoryCache.chain(sqlCache) val apolloClient: ApolloClient = ApolloClient.Builder() .serverUrl(SERVER_URL) .normalizedCache(memoryThenSqlCache) .build()
  22. The cache updates after a mutation mutation { updateUser({ id:

    "42", status: "Au DevFest Lille 😃" }) { id status } }
  23. The cache updates after a mutation watch() // receives from

    network "En télétravail 🏡" // wait for cache updates mutate("Au DevFest Lille 😃") // receives from network "Au DevFest Lille 😃" // updates the cache "Au DevFest Lille 😃" Coroutine 1 Coroutine 2
  24. Conclusion • Type-safe language + Tooling = 💜 • Offline

    support is one line 😎 • Don’t forget your ids!
  25. Where to go from there • Apollo-Kotlin ◦ #3566 (data

    age) ◦ #3807 (pagination) • Server side caching ◦ @cacheControl ◦ Automated Persisted Queries
  26. Declarative cache type User { id: ID! name: String! }

    type Query { user(id: ID!): User } extend type User @typePolicy(keyFields: "id") extend type Query @fieldPolicy(forField: "user", keyArgs: "id")
  27. Schema # schema.graphqls type Speaker implements Node { id: ID!

    name: String! company: String session(name: String!): Session sessions(first: Int, after: ID, orderBy: SessionOrder): [Session!] }