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
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
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
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
46
Paris Kotlin Meetup de mai: Gradle 💙 Kotlin
martinbonnin
3
95
Offline and Reactive apps with Apollo Kotlin
martinbonnin
1
74
Other Decks in Programming
See All in Programming
PHPだって関数型したい 〜できること、できないこと〜 / fp-in-php
jsoizo
1
260
Apache Hive: そしてCloud Native Lakehouseへ
okumin
1
190
わからない話を追いかけたら、プログラミング言語を作る側にいた
ydah
2
130
なぜ関数型プログラミングで「型」と「証明」が語られるのか #fp_matsuri
kajitack
3
1.1k
Prismを使った型安全な暗号化_関数型まつり2026
_fhhmm
0
160
数百円から始めるRuby電子工作
tarosay
0
120
人間の目はかわらない、だからJPEGは30年もつ
yuzneri
12
17k
ドリフトを絶対に許さない(?)CDK運用 / CDK Ops with Zero Tolerance for Drifts (?)
akihisaikeda
1
130
AIエージェントで 変わるAndroid開発環境
takahirom
2
750
AI Engineeringは、AIプロダクトだけのものか? 〜AIがソフトウェアを作る時代の新しい当たり前〜 / No AI in your product. AI Engineering in your development.
rkaga
4
310
Build-to-own AI: Agentic Development for Humans
inesmontani
PRO
0
130
JAWS-UG横浜 #102 AWSサ終供養LT会 成仏できない AWS サービスたち 〜本日、三体供養します〜
maroon1st
0
280
Featured
See All Featured
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
160
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
470
Chasing Engaging Ingredients in Design
codingconduct
0
250
Embracing the Ebb and Flow
colly
88
5.1k
HDC tutorial
michielstock
2
760
What does AI have to do with Human Rights?
axbom
PRO
1
2.3k
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
410
Max Prin - Stacking Signals: How International SEO Comes Together (And Falls Apart)
techseoconnect
PRO
0
330
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
520
Facilitating Awesome Meetings
lara
57
7k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
230
How to Ace a Technical Interview
jacobian
281
24k
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!