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
mbonnin
November 07, 2022
Technology
74
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Offline and Reactive apps with Apollo Kotlin
Slides from Benoit Lubek and Martin Bonnin's talk at droidcon London 2022 🎃
mbonnin
November 07, 2022
More Decks by mbonnin
See All by mbonnin
Metadataquoi??
martinbonnin
0
150
Harmonizing APIs, a comparison of GraphQL and OpenAPI through the Spotify API
martinbonnin
1
94
Construisez votre bibliothèque Java/Kotlin
martinbonnin
2
120
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
100
GraphQL_nullability__state_of_the_union.pdf
martinbonnin
1
48
Paris Kotlin Meetup de mai: Gradle 💙 Kotlin
martinbonnin
3
96
What's new in Apollo Kotlin 3
martinbonnin
2
260
Other Decks in Technology
See All in Technology
20260807_第6回_関東kaggler会LT_claw系bot xangiと始める、"寂しくない" kaggle
sugupoko
0
270
生成 AI の基礎 〜 サンプル実装で学ぶ基本原理
enakai00
7
4.4k
DatadogのBits Chatが開発組織にもたらしたもの / What Bits Chat Has Brought Us
sms_tech
1
270
JavaScript 研修 (2026)
recruitengineers
PRO
2
540
ガバメント AI 源内を地方自治体は活用できるのか可能性と課題、期待について
takeda_h
1
410
Agent 時代の Kaggle 展望 / kaggle-in-the-agentic-era
upura
1
720
取引先から届く 「セキュリティチェックシート」の読み解き方
kamadamakoto
0
150
変化の早いClaude Codeを 書籍に落とし込む
oikon48
7
1.4k
Forza Horizon 6 のテレメトリ機能で 自動運転に使えそうな学習データを集める話
henjin0
0
170
猫付きpingコマンドを自作
uyuki234
0
230
私がブラウザを自作したくなった理由
supurazako
1
230
グローバル基準のSREは、運用現場でどう機能したか:成熟度アセスメントの実践 / SRE NEXT 2026
sorawatanabe
0
230
Featured
See All Featured
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
410
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
2.3k
Paper Plane
katiecoart
PRO
2
53k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.3k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.2k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Typedesign – Prime Four
hannesfritz
42
3.1k
The SEO identity crisis: Don't let AI make you average
varn
0
530
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
63
45k
Balancing Empowerment & Direction
lara
6
1.2k
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, Unions ◦ Lists ◦ Nullability • Introspection • Deprecation
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
How to deal with partial entities query ViewerQuery { #
Returns a User viewer { id email avatarUrl } } query UserQuery($id: String) { # Also a User user(id: $id) { id email login name } }
How to deal with partial entities
How to deal with partial entities null means not cached?
null means null?
How to deal with partial entities 👎 both are User
- should share cache
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" } } } key: ViewerQuery key: UserQuery(42) same entity, but different keys 👎
Entering Cache normalization Response → List<Record> A Record is a
Map<String, Any?>
Cache normalization { "data": { "user": { "id": "42", "email":
"
[email protected]
", "login": "BoD", "name": "Benoit Lubek" } } } Response
Cache normalization { "data": { "user": CacheReference("42"), }, "42": {
"id": "42", "email": "
[email protected]
", "login": "BoD", "name": "Benoit Lubek" } } { "data": { "user": { "id": "42", "email": "
[email protected]
", "login": "BoD", "name": "Benoit Lubek" } } } Response 2 Records
Adding fields { "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": "https://…", } }
Adding fields { "data": { "user": CacheReference("42"), }, "42": {
"id": "42", "email": "
[email protected]
", "login": "BoD", "name": "Benoit Lubek", // New Record field "avatarUrl": "https://…", } } Ids!
What if there’s no id? { "data": { "user": {
"email": "
[email protected]
", "login": "BoD", "name": "Benoit Lubek" } } }
Use the field’s path as key { "data": { "user":
CacheReference("data.user"), }, "data.user": { "email": "
[email protected]
", "login": "BoD", "name": "Benoit Lubek" } }
What if there are several paths? { "data": { "user":
{…} "comments": [ { "text": "#dcldn22 is awesome 😎", "date": "2022-10-28T10:00Z", "user": { "login": "BoD", "avatarUrl": "https://" }, }, ], } }
Use the field’s path as key { "data": { "user":
CacheReference("data.user"), "comments": [CacheReference("data.comments[0]")], }, "data.user": { "email": "
[email protected]
", "login": "BoD", "name": "Benoit Lubek" }, "data.comments[0]": { "title": "Write retrowave slides!", "checked": true, "user": CacheReference("data.comments[0].user") }, "data.comments[0].user": { "login": "BoD", "avatarUrl": "https://" } } Duplication
Always query 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 ) ) 2 Records 1 Data class
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: "At dcldn22 😃") { id status } }
The cache updates after a mutation watch() mutate("At dcldn22 😃")
// receives from network "At dcldn22 😃" // updates the cache "At dcldn22 😃" Coroutine 1 Coroutine 2 // receives from network "Work from home 🏡" // wait for cache updates
Single source of truth
Conclusion • Type-safe language + Tooling = 💜 • Offline
support is one line ✈ • Don’t forget your ids!
Where to go from here • Declarative cache ◦ Available
in 3.0 • Client improvements ◦ apollo-normalized-cache-incubating ◦ #3566 (data age) ◦ #3807 (pagination) • Server side caching ◦ @cacheControl ◦ Automated Persisted Queries
For inspiration 🎊 github.com/joreilly/Confetti
Merci! @BoD @MartinBonnin apollographql/apollo-kotlin
It depends.
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")
Optimistic updates
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")
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!] }
Caching entities