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
55
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
97
Harmonizing APIs, a comparison of GraphQL and OpenAPI through the Spotify API
martinbonnin
1
39
Construisez votre bibliothèque Java/Kotlin
martinbonnin
2
88
Building libraries for the next 25 years
martinbonnin
2
54
Gratatouille: metaprogramming for your build-logic
martinbonnin
2
140
GraphQL 💙 Kotlin, 2024 edition
martinbonnin
1
63
GraphQL_nullability__state_of_the_union.pdf
martinbonnin
1
33
Paris Kotlin Meetup de mai: Gradle 💙 Kotlin
martinbonnin
3
67
What's new in Apollo Kotlin 3
martinbonnin
2
200
Other Decks in Technology
See All in Technology
【CEDEC2025】現場を理解して実現!ゲーム開発を効率化するWebサービスの開発と、利用促進のための継続的な改善
cygames
PRO
0
370
スプリントレビューを効果的にするために
miholovesq
9
1.7k
増え続ける脆弱性に立ち向かう: 事前対策と優先度づけによる 持続可能な脆弱性管理 / Confronting the Rise of Vulnerabilities: Sustainable Management Through Proactive Measures and Prioritization
nttcom
1
210
完璧を目指さない小さく始める信頼性向上
kakehashi
PRO
0
110
「AI駆動開発」のボトルネック『言語化』を効率化するには
taniiicom
1
210
M365アカウント侵害時の初動対応
lhazy
7
5.1k
自分がLinc’wellで提供しているプロダクトを理解するためにやったこと
murabayashi
1
170
少人数でも回る! DevinとPlaybookで支える運用改善
ishikawa_pro
4
1.7k
MCPに潜むセキュリティリスクを考えてみる
milix_m
1
870
分散トレーシングによる コネクティッドカーのデータ処理見える化の試み
thatsdone
0
270
AI時代の知識創造 ─GeminiとSECIモデルで読み解く “暗黙知”と創造の境界線
nyagasan
0
160
Kiroから考える AIコーディングツールの潮流
s4yuba
1
290
Featured
See All Featured
We Have a Design System, Now What?
morganepeng
53
7.7k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
33
2.4k
Unsuck your backbone
ammeep
671
58k
Navigating Team Friction
lara
187
15k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
138
34k
Scaling GitHub
holman
461
140k
It's Worth the Effort
3n
185
28k
Mobile First: as difficult as doing things right
swwweet
223
9.7k
A Modern Web Designer's Workflow
chriscoyier
695
190k
How GitHub (no longer) Works
holman
314
140k
The Invisible Side of Design
smashingmag
301
51k
GitHub's CSS Performance
jonrohan
1031
460k
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