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": "[email protected]", "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": "[email protected]", "avatarUrl": "http://…" } } } { "data": { "user": { "id": "42", "email": "[email protected]", "login": "BoD", "name": "Benoit Lubek" } } }
  8. Cache normalization - Response { "data": { "user": { "id":

    "42", "email": "[email protected]", "login": "BoD", "name": "Benoit Lubek" } } }
  9. Cache normalization - Records { "data": { "user": CacheReference("42"), },

    "42": { "id": "42", "email": "[email protected]", "login": "BoD", "name": "Benoit Lubek" } }
  10. Adding fields { "data": { "user": CacheReference("42"), }, "42": {

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

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

    "email": "[email protected]", "login": "BoD", "name": "Benoit Lubek" } } }
  13. The field path is used as id { "data": {

    "user": CacheReference("data.user"), }, "data.user": { "email": "[email protected]", "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": "[email protected]", "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": "[email protected]", "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": "[email protected]", "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": "[email protected]", "login": "BoD", "name": "Benoit Lubek" } } Data( user=User( id=42, [email protected], 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!] }