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
1
59
Offline and Reactive apps with Apollo Kotlin
Slides from Benoit Lubek and Martin Bonnin's talk at droidcon London 2022 🎃
mbonnin
November 07, 2022
Tweet
Share
More Decks by mbonnin
See All by mbonnin
Metadataquoi??
martinbonnin
0
110
Harmonizing APIs, a comparison of GraphQL and OpenAPI through the Spotify API
martinbonnin
1
49
Construisez votre bibliothèque Java/Kotlin
martinbonnin
2
89
Building libraries for the next 25 years
martinbonnin
2
61
Gratatouille: metaprogramming for your build-logic
martinbonnin
2
150
GraphQL 💙 Kotlin, 2024 edition
martinbonnin
1
70
GraphQL_nullability__state_of_the_union.pdf
martinbonnin
1
34
Paris Kotlin Meetup de mai: Gradle 💙 Kotlin
martinbonnin
3
71
What's new in Apollo Kotlin 3
martinbonnin
2
220
Other Decks in Technology
See All in Technology
CLIPでマルチモーダル画像検索 →とても良い
wm3
2
800
AI時代の発信活動 ~技術者として認知してもらうための発信法~ / 20251028 Masaki Okuda
shift_evolve
PRO
1
140
NOT A HOTEL SOFTWARE DECK (2025/11/06)
notahotel
0
3k
AI-ready"のための"データ基盤 〜 LLMOpsで事業貢献するための基盤づくり
ismk
0
110
251029 JAWS-UG AI/ML 退屈なことはQDevにやらせよう
otakensh
0
190
Oracle Database@Google Cloud:サービス概要のご紹介
oracle4engineer
PRO
0
430
AIとの協業で実現!レガシーコードをKotlinらしく生まれ変わらせる実践ガイド
zozotech
PRO
2
330
プロダクトエンジニアとしてのマインドセットの育み方 / How to improve product engineer mindset
saka2jp
1
180
kotlin-lsp の開発開始に触発されて、Emacs で Kotlin 開発に挑戦した記録 / kotlin‑lsp as a Catalyst: My Journey to Kotlin Development in Emacs
nabeo
2
320
データエンジニアとして生存するために 〜界隈を盛り上げる「お祭り」が必要な理由〜 / data_summit_findy_Session_1
sansan_randd
1
960
日本のソブリンAIを支えるエヌビディアの生成AIエコシステム
acceleratedmu3n
0
130
InsightX 会社説明資料/ Company deck
insightx
0
200
Featured
See All Featured
Mobile First: as difficult as doing things right
swwweet
225
10k
RailsConf 2023
tenderlove
30
1.3k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
1.7k
Building Flexible Design Systems
yeseniaperezcruz
329
39k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
36
6.1k
Why Our Code Smells
bkeepers
PRO
340
57k
A Tale of Four Properties
chriscoyier
161
23k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
10
640
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
48
9.7k
Stop Working from a Prison Cell
hatefulcrawdad
272
21k
How to Think Like a Performance Engineer
csswizardry
27
2.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