Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Implementation of API call state using sealed c...
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Hideyuki Kikuma
January 16, 2020
Programming
2.8k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Implementation of API call state using sealed class
Hideyuki Kikuma
January 16, 2020
More Decks by Hideyuki Kikuma
See All by Hideyuki Kikuma
Talk about still using minSdkVersion=7
hidey
1
580
AndroidとIPv6
hidey
1
1.2k
Other Decks in Programming
See All in Programming
IBM Bob Dojo #1 仕様駆動開発入門
oniak3ibm
PRO
0
390
Atomic Design, Enforced: Scaling Mobile Design Systems (next.app devCon / droidCon Berlin 2026)
steliosf
PRO
0
170
コードレビューのボトルネックを"する側"と"される側"の両面から解消する
yub0n
2
1.6k
AWS CDKのカスタムリソースでContinuum(旧Security Agent)を実装した話 | Implementing Continuum (formerly Security Agent) via AWS CDK Custom Resource
akihisaikeda
2
150
残高管理から台帳サービスへの進化
artoy
1
200
[JA] Domain Modeling with Vanilla Rails: 「操作」から再考するRailsの設計論
yasaichi
0
260
Next.js: The final boss of Web Performance
guaca
0
260
コーディング自動化の次へ。人の介入をゼロにするファクトリーとサポートAIによる実践
timakin
5
3.7k
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Kaigi on Rails 2026, Tokyo, Japan
marcoroth
2
230
Verilogで学ぶCPU自作入門
uyuki234
8
4.1k
JIS keyboard support in Omarchy
lef237
0
120
Railsのためのパスキー実践入門 〜マルチテナントSaaSでの意思決定〜
utakaha
2
580
Featured
See All Featured
Code Review Best Practice
trishagee
74
20k
Rebuilding a faster, lazier Slack
samanthasiow
85
9.7k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
270
Joys of Absence: A Defence of Solitary Play
codingconduct
1
550
The Invisible Side of Design
smashingmag
301
52k
The Art of Programming - Codeland 2020
erikaheidi
57
14k
Building a Scalable Design System with Sketch
lauravandoore
464
34k
Faster Mobile Websites
deanohume
310
32k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
74
43k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.8k
Transcript
API通信の状態を sealed classを使って表現する Bonfire Android #6
自己紹介 @hidey / 菊間 英行 株式会社メルペイ Android エンジニア DroidKaigi スタッフ
API通信中の状態とか 困ったりしてませんか?
よくある画面表示の要件 • 画面表示には API から取ってきた情報が必要 ◦ 初期表示はデフォルト表示の場合と何も表示したくないパターンどっちもありそう • ロード中は progress
表示にしたい • API のレスポンスが返ってきたらその内容を表示 • API がエラーだった場合はエラー画面表示にしたい • エラー画面に再読み込みボタンを付けたい
None
つまり画面の状態としてはざっくり4つ • 初期状態 • ロード中 • データ取得完了 • データ取得エラー
メルペイではRemoteDataKを使っている 内部で使っていたライブラリを OSS 化したもの • 状態を表現する sealed class • 便利関数
(map, mapError など ) メルペイの用途ではこれで満足できている https://github.com/mercari/RemoteDataK
コードで表すとこんな感じ sealed class RemoteData<out V : Any, out E :
Exception> { object Initial : RemoteData<Nothing, Nothing>() class Loading<V : Any>(progress: Int? = null, val total: Int = 100) : RemoteData<V, Nothing>() class Success<out V : Any>(val value: V) : RemoteData<V, Nothing>() class Failure<out E : Exception>(val error: E) : RemoteData<Nothing, E>() }
素朴なstate実装 data class SampleState( val entity: SampleEntity? = null, val
error: Exception? = null, val isLoading: Boolean = false ) data class SampleEntity( val title: String, val items: List<String> )
その場合のStateの変更箇所のコード class SampleReducer { fun reduce(action: Action, currentState: SampleState): SampleState
= when (action) { is ShowDataAction -> { if (action.result.isSuccess) { currentState.copy(entity = action.result.entity, isLoading = false) } else { currentState.copy(error = action.result.error, isLoading = false) } } is LoadData -> currentState.copy(isLoading = true, entity = null, error = null) else -> currentState } }
その場合のView周りのコード fun updateView(state: SampleState) { if (state.entity != null) {
titleView.text = state.entity.title adapter.items = state.entity.items } if (state.error != null) { errorMessage.text = state.error.localizedMessage } errorView.isVisible = state.error != null loadingView.isVisible = state.isLoading }
RemoteDataで書き直してみる data class SampleState( val entity: RemoteData<SampleEntity, Exception> = RemoteData.Initial
) data class SampleEntity( val title: String, val items: List<String> )
その場合のStateの変更箇所のコード class SampleReducer { fun reduce(action: Action, currentState: SampleState): SampleState
= when (action) { is ShowDataAction -> { if (action.result.isSuccess) { currentState.copy(entity = RemoteData.Success(action.result.entity)) } else { currentState.copy(entity = RemoteData.Failure(action.result.error)) } } is LoadData -> currentState.copy(entity = RemoteData.Loading()) else -> currentState } }
その場合のView周りのコード fun updateView(state: SampleState) { when (val entity = state.entity)
{ is RemoteData.Success -> { titleView.text = entity.value.title adapter.items = entity.value.items } is RemoteData.Failure -> errorMessage.text = entity.error.localizedMessage } errorView.isVisible = state.entity.isFailure loadingView.isVisible = state.entity.isLoading }
あれ? そんなに変わらなくない?
拡張関数を追加してみる fun <V : Any, E : RemoteError> Result<V, E>.toRemoteData():
RemoteData<V, E> = when (this) { is Result.Success -> RemoteData.Success(this.value) is Result.Failure -> RemoteData.Failure(this.error) }
Stateの変更箇所のコード class SampleReducer { fun reduce(action: Action, currentState: SampleState): SampleState
= when (action) { is ShowDataAction -> currentState.copy( entity = action.result.toRemoteData() ) is LoadData -> currentState.copy(entity = RemoteData.Loading()) else -> currentState } }
View周りでいいこととか statusObservable.map(SampleState::entity) .map { it.value.title } // title が nonnull
である必要がある .distinctUntilChanged() .subscribe { updateTitle(it) }
課題もある
例えばkotlinx.serializationが使えない • AAC の ViewModel で状態管理をしたい場合、 Bundle に入れられる型が必要 • kotlinx.serialization
で Serializable にしたい • RemoteDataK の Failure は java の Exception を持っている • これは kotlinx.serialization で Serializable に出来ない このため、アドホックなコードで対応してる
詳しくはこちら https://tech.mercari.com/entry/2019/12/04/100000 https://tech.mercari.com/entry/2019/12/18/100000
今日のサンプルコード https://github.com/hidey/remote_data_sample
まとめ • sealed class を使うと状態 + 値の組み合わせをうまく表現できる • 一つのプロパティにまとまるので関数で処理しやすい •
ロード中などの状態を全体で統一した表現に出来る • NullObject を作らずに nonnull に出来るので Rx と相性がいい
ご静聴ありがとうございました