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
90
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
99
GraphQL_nullability__state_of_the_union.pdf
martinbonnin
1
43
Paris Kotlin Meetup de mai: Gradle 💙 Kotlin
martinbonnin
3
92
Offline and Reactive apps with Apollo Kotlin
martinbonnin
1
71
Other Decks in Programming
See All in Programming
AI時代の仕事技芸論〜ソフトウェア開発で「遊ぶように働く」職人的熟達のすすめ(スクフェス仙台 2026バージョン)
kuranuki
0
260
過去最大のMCPアップデート! 2026-07-28 RC版の謎に迫る
licux
6
450
SREの積み重ねがAI駆動開発のガードレールになった ― 7つの実践/SRE Guardrails The 7
tomoyakitaura
7
1.2k
AIを活用したE2Eテスト実装効率化のあゆみ / ebisu-mobile-14-kotetu
kotetuco
0
160
LLMによるContent Moderationの本番運用の裏側と品質担保への挑戦
suikabar
3
820
Even G2とAWSで推しのエージェントを召喚しよう!
har1101
1
140
The Bowling Game- From Imperative to Functional Programming - Part 1
philipschwarz
PRO
0
260
Strategic Design in the Frontend: Moduliths & Micro Frontends @DDDEurope
manfredsteyer
PRO
0
140
霧の中の代数的エフェクト
funnyycat
1
170
Performance Engineering for Everyone
elenatanasoiu
0
250
エンジニアにデザインハーネスを 〜デザインプロセスを規定するためのハーネス〜 / Design harness from an engineer's perspective
rkaga
2
710
AI 輔助遺留系統現代化的經驗分享
jame2408
1
1.2k
Featured
See All Featured
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2k
Utilizing Notion as your number one productivity tool
mfonobong
4
340
AI Search: Where Are We & What Can We Do About It?
aleyda
0
7.7k
Typedesign – Prime Four
hannesfritz
42
3.1k
Large-scale JavaScript Application Architecture
addyosmani
515
110k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
260
Fireside Chat
paigeccino
42
4k
The SEO identity crisis: Don't let AI make you average
varn
0
510
Facilitating Awesome Meetings
lara
57
7k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
72
40k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.8k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
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!