Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Offline and Reactive apps with Apollo Kotlin
Search
mbonnin
June 11, 2022
Programming
280
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
170
Harmonizing APIs, a comparison of GraphQL and OpenAPI through the Spotify API
martinbonnin
1
120
Construisez votre bibliothèque Java/Kotlin
martinbonnin
2
130
Building libraries for the next 25 years
martinbonnin
2
140
Gratatouille: metaprogramming for your build-logic
martinbonnin
2
210
GraphQL 💙 Kotlin, 2024 edition
martinbonnin
1
120
GraphQL_nullability__state_of_the_union.pdf
martinbonnin
1
63
Paris Kotlin Meetup de mai: Gradle 💙 Kotlin
martinbonnin
3
99
Offline and Reactive apps with Apollo Kotlin
martinbonnin
1
91
Other Decks in Programming
See All in Programming
Modernizing iOS Apps
seyfoyun
0
150
現場と育てるRailsモデリング 〜Spreadsheetから始めたモデル設計〜
siroemk
2
1.2k
作って学ぶSpring BootのAuto Configuration / JJUG Night Seminar 2026-10-19
rshindo
0
220
AI時代のTerminal環境を UNIX哲学で設計・運用する
yasunori0418
1
350
はじめてのPLATEAU
nokonoko1203
1
580
エージェントを使い続けるためのメタワーク / Meta-work for keeping using the agent
rkaga
6
2.2k
Spec-Driven Development - Why Vaadin Is the Perfect Fit
simas
PRO
0
230
高橋メソッドでCVE解説シリーズ 〜CVE-2026-112816〜
hakkadaikon
0
230
まずはコードより業務と向き合う:テーブル解体のためにPdMを呼んだ話
sakawe_ee
0
180
Optunaを作ったときにやりたかったこと、その後 (2026/10/16, Optuna Meetup #3)
iwiwi
4
1.5k
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
150
AIが生成したコードは読むべきか?抽象化設計とコードの品質保証 / abstraction-design-and-quality
minodriven
16
8.8k
Featured
See All Featured
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
3.1k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.4k
Fireside Chat
paigeccino
43
4.1k
The #1 spot is gone: here's how to win anyway
tamaranovitovic
4
1.2k
StorybookのUI Testing Handbookを読んだ
zakiyama
31
7k
Building Applications with DynamoDB
mza
96
7.3k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
123
22k
Reality Check: Gamification 10 Years Later
codingconduct
0
2.4k
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
450
Designing for humans not robots
tammielis
254
26k
Statistics for Hackers
jakevdp
799
230k
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
2
6.4k
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!