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
66
1
Share
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
130
Harmonizing APIs, a comparison of GraphQL and OpenAPI through the Spotify API
martinbonnin
1
75
Construisez votre bibliothèque Java/Kotlin
martinbonnin
2
100
Building libraries for the next 25 years
martinbonnin
2
98
Gratatouille: metaprogramming for your build-logic
martinbonnin
2
170
GraphQL 💙 Kotlin, 2024 edition
martinbonnin
1
80
GraphQL_nullability__state_of_the_union.pdf
martinbonnin
1
38
Paris Kotlin Meetup de mai: Gradle 💙 Kotlin
martinbonnin
3
76
What's new in Apollo Kotlin 3
martinbonnin
2
240
Other Decks in Technology
See All in Technology
遊びで始めたNew Relic MCP、気づいたらChatOpsなオブザーバビリティボットができてました/From New Relic MCP to a ChatOps Observability Bot
aeonpeople
1
150
Oracle Cloud Infrastructure(OCI):Onboarding Session(はじめてのOCI/Oracle Supportご利⽤ガイド)
oracle4engineer
PRO
2
17k
OPENLOGI Company Profile
hr01
0
83k
「できない」のアウトプット 同人誌『精神を壊してからの』シリーズ出版を 通して得られたこと
comi190327
3
520
パワポ作るマンをMCP Apps化してみた
iwamot
PRO
0
290
AIエージェント勉強会第3回 エージェンティックAIの時代がやってきた
ymiya55
0
220
PostgreSQL 18のNOT ENFORCEDな制約とDEFERRABLEの関係
yahonda
1
210
Network Firewall Proxyで 自前プロキシを消し去ることができるのか
gusandayo
0
170
Microsoft Fabricで考える非構造データのAI活用
ryomaru0825
0
610
Even G2 クイックスタートガイド(日本語版)
vrshinobi1
0
190
第26回FA設備技術勉強会 - Claude/Claude_codeでデータ分析 -
happysamurai294
0
340
Databricks Appsで実現する社内向けAIアプリ開発の効率化
r_miura
0
230
Featured
See All Featured
Being A Developer After 40
akosma
91
590k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
260
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
400
Done Done
chrislema
186
16k
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
160
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
Darren the Foodie - Storyboard
khoart
PRO
3
3.1k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.3k
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
700
Side Projects
sachag
455
43k
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
340
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
160
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