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
70
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Offline and Reactive apps with Apollo Kotlin
Slides from Benoit Lubek and Martin Bonnin's talk at droidcon London 2022 🎃
mbonnin
November 07, 2022
More Decks by mbonnin
See All by mbonnin
Metadataquoi??
martinbonnin
0
140
Harmonizing APIs, a comparison of GraphQL and OpenAPI through the Spotify API
martinbonnin
1
89
Construisez votre bibliothèque Java/Kotlin
martinbonnin
2
110
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
99
GraphQL_nullability__state_of_the_union.pdf
martinbonnin
1
43
Paris Kotlin Meetup de mai: Gradle 💙 Kotlin
martinbonnin
3
91
What's new in Apollo Kotlin 3
martinbonnin
2
250
Other Decks in Technology
See All in Technology
クレデンシャル流出 ― 攻撃 3 時間 vs 復旧 10 時間。この非対称性にどう備えるか
kazzpapa3
3
620
iOS アプリの「これって不具合ですか?」を AI に調べてもらう
miichan
0
150
AWS Security Hub CSPMの成功・失敗体験
cmusudakeisuke
0
590
自作お家AIエージェントスタックチャンFWで困っている所紹介
74th
0
160
データレイクの「見えない問題」を可視化する
sansantech
PRO
1
230
Flow 不死:AI 時代 DevOps 的不變本質
cheng_wei_chen
2
550
4人目のSREはAgent
tanimuyk
0
280
AIをフル活用してオンコール機能のプロトタイプを2日で作った話 / Building an AI-Powered On-Call Prototype in Just Two Days
nari_ex
0
150
5分でわかる Amazon Connect_20260608
hwangbyeonghun
0
130
從開發到部署全都交給 AI:實作 AI 驅動的自動化流程
appleboy
0
180
ご挨拶「10周年を迎える共創ラボのこれまでとこれから」
iotcomjpadmin
0
150
Why is RC4 still being used?
tamaiyutaro
0
170
Featured
See All Featured
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
1
790
Building AI with AI
inesmontani
PRO
1
1.1k
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
210
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.1k
Balancing Empowerment & Direction
lara
6
1.2k
Tell your own story through comics
letsgokoyo
1
980
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
170
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
1
3.6k
Done Done
chrislema
186
16k
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.5k
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
140
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