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
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
JAWS-UG横浜 #102 AWSサ終供養LT会 成仏できない AWS サービスたち 〜本日、三体供養します〜
maroon1st
0
280
信頼性について考えてみる(SRE NEXT 2026 miniLT)
hayama17
0
230
PHPだって関数型したい 〜できること、できないこと〜 / fp-in-php
jsoizo
1
260
PHP初心者セッション2026 〜生成AIでは見えない裏側を知る:今だからLAMPを通して仕組みを学ぶ〜
kashioka
0
750
今さら聞けない .NET CLI
htkym
0
170
Claude Team Plan導入・ガイド
tk3fftk
0
240
Prismを使った型安全な暗号化_関数型まつり2026
_fhhmm
0
160
なぜ関数型プログラミングで「型」と「証明」が語られるのか #fp_matsuri
kajitack
3
1.1k
torikago - Ruby::Boxで照らすモジュラモノリスの実行境界
se4weed
1
290
アルゴリズムは何を圧縮しているのか ─ Haskell から育った「圧縮代数」というメンタルモデル
naoya
16
3.7k
使用 Meilisearch 建立新聞搜尋工具
johnroyer
0
190
Laravelで学ぶ Webアプリケーションチューニング入門/web_application_tuning_101
hanhan1978
4
1.5k
Featured
See All Featured
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
280
Future Trends and Review - Lecture 12 - Web Technologies (1019888BNR)
signer
PRO
0
3.7k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
How to make the Groovebox
asonas
2
2.3k
Chasing Engaging Ingredients in Design
codingconduct
0
250
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
420
WCS-LA-2024
lcolladotor
0
780
Skip the Path - Find Your Career Trail
mkilby
1
170
Producing Creativity
orderedlist
PRO
348
40k
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
620
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
2.9k
The B2B funnel & how to create a winning content strategy
katarinadahlin
PRO
1
450
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!