Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Offline and Reactive apps with Apollo Kotlin
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
mbonnin
June 11, 2022
Programming
260
3
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Offline and Reactive apps with Apollo Kotlin
mbonnin
June 11, 2022
More Decks by mbonnin
See All by mbonnin
Metadataquoi??
martinbonnin
0
140
Harmonizing APIs, a comparison of GraphQL and OpenAPI through the Spotify API
martinbonnin
1
86
Construisez votre bibliothèque Java/Kotlin
martinbonnin
2
110
Building libraries for the next 25 years
martinbonnin
2
120
Gratatouille: metaprogramming for your build-logic
martinbonnin
2
190
GraphQL 💙 Kotlin, 2024 edition
martinbonnin
1
97
GraphQL_nullability__state_of_the_union.pdf
martinbonnin
1
43
Paris Kotlin Meetup de mai: Gradle 💙 Kotlin
martinbonnin
3
91
Offline and Reactive apps with Apollo Kotlin
martinbonnin
1
70
Other Decks in Programming
See All in Programming
Creating Composable Callables in Contemporary C++
rollbear
0
110
作って学ぶ、 JSX (TSX) ランタイムの基本
syumai
7
1.6k
気づいたらRubyで100作品 ー クリエイティブコーディングが生活の一部になるまで / 100 Ruby Sketches Later: How Creative Coding Became Part of My Life
chobishiba
3
570
IBM Bobを活用したレガシーアプリの最新化
oniak3ibm
PRO
1
190
Hunting Vulnerabilities in Symfony with LLMs
vinceamstoutz
0
540
ECSアプリログをFireLensでコスト削減しようとしたけど諦めた話 in Fargate×Node.js
akihisaikeda
2
4.1k
JavaDoc 再入門
nagise
0
330
軽量Java基盤の設計 DIコンテナに頼らない、長期保守と1秒起動の実現 JJUG CCC 2026 Spring
macha64
0
510
代数的データ型って何が嬉しいの? #frontend_phpcon_do
kajitack
8
3.5k
依存関係から依存物へ―Dependencyという言葉の歴史をひも解く
j_lee
0
120
Oxcを導入して開発体験が向上した話
yug1224
4
310
Vite+ Unified Toolchain for the Web
naokihaba
0
290
Featured
See All Featured
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.2k
We Have a Design System, Now What?
morganepeng
55
8.2k
Writing Fast Ruby
sferik
630
63k
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
420
Making Projects Easy
brettharned
120
6.7k
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
160
Statistics for Hackers
jakevdp
799
230k
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
Test your architecture with Archunit
thirion
1
2.3k
Winning Ecommerce Organic Search in an AI Era - #searchnstuff2025
aleyda
1
2k
The SEO Collaboration Effect
kristinabergwall1
1
480
How to Talk to Developers About Accessibility
jct
2
230
Transcript
Caching your data graph Offline & Reactive apps with Apollo
Kotlin
Hello, World! @BoD @MartinBonnin apollographql/apollo-kotlin
What is GraphQL? An open source language to describe and
run your API
What is GraphQL? • Schema ◦ Int, Float, String, Boolean
◦ Objects, Interfaces ◦ Lists ◦ Nullability • Introspection • Deprecation (and experimental soon 🧪)
APIs in a REST world
https://apis.guru/graphql-voyager/
How does it look in practice query UserQuery { user
{ id login } }
How does it look in practice query UserQuery { user
{ id login avatar { small medium } } }
How does it look in practice query UserQuery { user
{ id login name } } { "data": { "user": { "id": "42", "login": "BoD", "name": "Benoit Lubek" } } }
How does it look in practice query UserQuery { user
{ id login name } } { "data": { "user": { "id": "42", "login": "BoD", "name": "Benoit Lubek" } } }
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" } } }
Caching entities
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 } }
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" } } }
Entering Cache normalization Response -> List<Record> A Record is a
Map<String, Any?>
Cache normalization - Response { "data": { "user": { "id":
"42", "email": "
[email protected]
", "login": "BoD", "name": "Benoit Lubek" } } }
Cache normalization - Records { "data": { "user": CacheReference("42"), },
"42": { "id": "42", "email": "
[email protected]
", "login": "BoD", "name": "Benoit Lubek" } }
Adding fields { "data": { "user": CacheReference("42"), }, "42": {
"id": "42", "email": "
[email protected]
", "login": "BoD", "name": "Benoit Lubek", // New Record field "avatarUrl": "http://…" } }
{ "data": { "user": CacheReference("42"), }, "42": { "id": "42",
"email": "
[email protected]
", "login": "BoD", "name": "Benoit Lubek", // New Record field "avatarUrl": "http://…" } } Cache ids Ids!
What if there’s no id? { "data": { "user": {
"email": "
[email protected]
", "login": "BoD", "name": "Benoit Lubek" } } }
The field path is used as id { "data": {
"user": CacheReference("data.user"), }, "data.user": { "email": "
[email protected]
", "login": "BoD", "name": "Benoit Lubek" } }
What if there are several paths? { "data": { "todo":
[ { "title": "Write retrowave slides!", "checked": true, "user": { "login": "BoD", "avatarUrl": "https://" }, }, ], } }
The field path is used as id { "data": {
"user": CacheReference("data.user"), }, "data.user": { "email": "
[email protected]
", "login": "BoD", "name": "Benoit Lubek" }, }
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///" } }
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
Always define your ids
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 ) )
Apollo Kotlin
Storage: in-memory or persistent val memoryCache = MemoryCacheFactory(maxSizeBytes = 5_000_000)
val apolloClient: ApolloClient = ApolloClient.Builder() .serverUrl(SERVER_URL) .normalizedCache(memoryCache) .build()
Storage: in-memory or persistent val sqlCache = SqlNormalizedCacheFactory(context, "app.db") val
apolloClient: ApolloClient = ApolloClient.Builder() .serverUrl(SERVER_URL) .normalizedCache(sqlCache) .build()
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()
Watchers
The cache updates after a mutation mutation { updateUser({ id:
"42", status: "Au DevFest Lille 😃" }) { id status } }
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
Single source of truth
Conclusion • Type-safe language + Tooling = 💜 • Offline
support is one line 😎 • Don’t forget your ids!
Where to go from there • Apollo-Kotlin ◦ #3566 (data
age) ◦ #3807 (pagination) • Server side caching ◦ @cacheControl ◦ Automated Persisted Queries
For inspiration 🎊 github.com/joreilly/Confetti/pull/44
Questions?
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")
It depends.
Optimistic updates
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!] }
Life is hard!