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
droidkaigi-2019
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Yuya Kaido
February 07, 2019
Programming
4.9k
7
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
droidkaigi-2019
Yuya Kaido
February 07, 2019
More Decks by Yuya Kaido
See All by Yuya Kaido
matching-dev-meetup-1
yuyakaido
1
240
minami-aoyama-night-6
yuyakaido
1
1.3k
eureka-meetup-10
yuyakaido
0
860
droidkaigi-2018
yuyakaido
4
6.3k
navitime-eureka-1
yuyakaido
0
120
droidkaigi-2017
yuyakaido
11
8.5k
retty-tech-cafe-8
yuyakaido
0
210
mti-eureka-tech-beer
yuyakaido
0
510
potatotips-33
yuyakaido
2
690
Other Decks in Programming
See All in Programming
パーソナライズPodcastを作った話
repunit11
0
140
モデルのリファクタリングが難しいと思ったら、そもそも複雑だったのはビジネス仕様だった ? / is-the-business-domain-the-real-complexity
hatsu38
0
470
残高管理から台帳サービスへの進化
artoy
1
200
dotNET_vulnclicker_intro.pdf
deka_morita
0
140
コードレビューのボトルネックを"する側"と"される側"の両面から解消する
yub0n
2
1.6k
技術的負債の返済は、AI時代の複利で効く投資 — 経営としての意思決定とその遂行
curekoshimizu
1
2.3k
コーディング自動化の次へ。人の介入をゼロにするファクトリーとサポートAIによる実践
timakin
1
1.5k
機械に任せるテスト、人が見るテスト ⽣成AIで引き直した、運⽤保守フェーズの線引き
dske104
0
180
Workers Cache を知る
syumai
0
360
動作中のプログラムの中身をリアルタイムに覗く / Realtime Debugger for CSharp with Roslyn
prota
1
1.9k
RAG の “R” を Swift で覗いてみる 〜「意味から探す」検索の仕組み〜
nao_randd
0
160
Optunaを作ったときにやりたかったこと、その後 (2026/10/16, Optuna Meetup #3)
iwiwi
0
100
Featured
See All Featured
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
2
940
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
330
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
Thoughts on Productivity
jonyablonski
76
5.4k
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
590
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.4k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.8k
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
260
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
4
4.4k
[RailsConf 2023] Rails as a piece of cake
palkan
59
7.1k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
1.1k
Darren the Foodie - Storyboard
khoart
PRO
4
4k
Transcript
Redux for Android yuyakaido DroidKaigi 2019
ࣗݾհ • ւ౻༏ʢ͔͍Ͳ͏Ώ͏ʣ • גࣜձࣾΤϨΧ • AndroidΤϯδχΞ • DroidKaigi •
2016 - ςετ͓͡͞Μ • 2017 - RxJavaͷΤϥʔϋϯυϦϯά • 2018 - ϚϧνϩάΠϯͷ࣮ํ๏ • 2019 - Redux for Android 2 yuyakaido
Pairs Japan / Global • ຊ࠷େͷϚονϯάαʔϏε • ɾؖࠃͰαʔϏεల։ • ձһɿ1000ສਓ
• Ϛονϯάɿ5600ສ • Χοϓϧɿ10ສ 3
࣍ • ͡Ίʹ • AndroidΞϓϦ։ൃʹ͓͚Δ·͍͠ɺଞͷϓϥοτϑΥʔϜͰͷղܾࡦ • جૅฤ • Reduxͷશମ૾ͱߏཁૉ •
αϯϓϧɿTODOΞϓϦ • Ԡ༻ฤ • ඇಉظॲཧɺը໘ભҠɺঢ়ଶมߋͷෛՙɺςετίʔυ • αϯϓϧɿGitHubΫϥΠΞϯτ • ͓ΘΓʹ • ReduxͷϝϦοτɾσϝϦοτ • ଞͷΞʔΩςΫνϟͱͷؔੑ • ·ͱΊ 4
͡Ίʹ 5
Android։ൃʹ͓͚Δ·͍͠ • OSґଘͷڍಈ • iOS༏ͳຊࢢ • ෳࡶͳঢ়ଶཧ • ը໘Λލ͍ͩঢ়ଶͷಉظ •
ϥΠϑαΠΫϧͱͷ͖߹͍ํ 6
ը໘Λލ͍ͩঢ়ଶͷಉظ • ෳͷը໘ͰಉҰͷใ͕දࣔ͞Ε͍ͯΔ߹ɺ͋Δը໘Ͱߦ ΘΕͨঢ়ଶมߋΛଞͷը໘ʹಉظ͢Δඞཁ͕͋Δ • ඪ४APIΛ༻͍ͯঢ়ଶΛಉظ͢Δ߹ɺը໘ؒͰํʹσʔ λͷΓऔΓΛߦ͏ • Կ͕ʁ •
ը໘͕ํʹґଘͯ͠Մಡੑ͕ѱ͍ 7
ϥΠϑαΠΫϧͱͷ͖߹͍ํ • ଟ͘ͷϥΠϑαΠΫϧ͕ଘࡏ͠ɺϥΠϑαΠΫϧʹ߹Θͤͯঢ় ଶΛཧ͢Δඞཁ͕͋Δ • ը໘ճసϝϞϦෆʹΑΔը໘ͷ࠶ੜͱ͍ͬͨΠϨΪϡϥʔ έʔεߟྀ͢Δඞཁ͕͋Δ • Կ͕ʁ •
ϥΠϑαΠΫϧͱঢ়ଶཧͱ͍͏ຊ࣭తʹແؔͷͷ͕ ಉډ͍ͯ͠Δ 8
ෳࡶͳঢ়ଶཧͷ • ঢ়ଶΛಉظ͢ΔͨΊʹը໘͕ํʹґଘ͍ͯ͠Δ • ϥΠϑαΠΫϧͱঢ়ଶཧ͕ಉډ͍ͯ͠Δ 9
ଞͷϓϥοτϑΥʔϜͰͷղܾࡦ • ୯ํσʔλϑϩʔͷߟ͑ํʹଇͬͯঢ়ଶཧΛߦ͏ͷ͕ओྲྀ • σʔλͷྲྀΕΛ୯ํʹ੍ݶ͢Δߟ͑ํ • ͜ΕʹΑͬͯঢ়ଶมߋʹ·ͭΘΔͷଟ͕͘ղফՄೳ • ୯ํσʔλϑϩʔͷ࣮ྫͱͯ͠ɺFacebook͕FluxΛఏএ •
ͦͷޙଟ͘ͷ࣮ྫ͕ొ͠ɺ࠷ϝδϟʔͳͷ͕Redux 10
Redux • ୯ํσʔλϑϩʔΛϕʔεͱͨ͠ΞʔΩςΫνϟ • ঢ়ଶΛதԝͰҰׅཧ͠ɺը໘ͦΕΛඳը͢Δ͚ͩ • ReduxͰঢ়ଶཧͷΛղফͰ͖ΔͷͰͳ͍͔ʁ • ঢ়ଶΛதԝͰҰׅཧ͢Δ͜ͱͰը໘͕ํʹґଘ͠ͳ͍ •
ঢ়ଶཧͱϥΠϑαΠΫϧΛ͢Δ͜ͱ͕Ͱ͖Δ 11
جૅฤ 12
Reduxͷߏཁૉ 13
State • ΞϓϦશମͷঢ়ଶΛදݱ͢Δͷ • ΞϓϦશମͷঢ়ଶΛ1ͭͷStateͰදݱ͢Δ • ෭࡞༻Λࢭ͢ΔͨΊʹಡΈऔΓઐ༻Ͱఆٛ͢Δͱྑ͍ 14
Action • ঢ়ଶͷมߋ༰Λදݱ͢Δͷ • ActionΛStoreʹ͢͜ͱͰঢ়ଶͷมߋΛߦ͏͜ͱ͕ग़དྷΔ 15
Reducer • StateͱAction͔ΒStateΛܭࢉ͢Δͷ • ܕͰදݱ͢Δͱɺ(State, Action) -> StateͱͳΔ • ෭࡞༻ͷͳ͍७ਮͳؔͱ࣮ͯ͢͠Δඞཁ͕͋Δ
16
Store • Stateͷอ࣋ɾมߋɾ௨Λߦ͏ͷ • ঢ়ଶมߋॲཧReducerΛ༻͍ͯߦ͏ • RxJavaΛར༻͢Δͱ࣮͕Γ͍͢ 17
αϯϓϧ • TodoΞϓϦ • TodoҰཡ • TodoՃ • ٕज़ཁૉ •
Reduxͷશମ૾ • ֤ཁૉͷ࣮ • ঢ়ଶมߋͷखॱ • ϦϙδτϦ • https://github.com/yuyakaido/ReduxKit 18
શମ૾ 19
Ϣʔεέʔε • TodoҰཡ • TodoՃ 20
AppState 21
AppState • ΞϓϦશମͷঢ়ଶΛදݱ͢ΔΫϥε • Data Classͱͯ͠ఆٛ͢Δͱঢ়ଶมߋָ͕ data class AppState( val
todos: List<Todo> = emptyList() ) : StateType data class Todo( val title: String ) 22
AppAction 23
AppAction • ঢ়ଶมߋΛදݱ͢ΔΫϥε • Sealed ClassͰ࣮͢Δ͜ͱͰReducerΛγϯϓϧʹͰ͖Δ sealed class AppAction :
ActionType { data class RefreshTodos(val todos: List<Todo>) : AppAction() data class AddTodo(val todo: Todo) : AppAction() } 24
AppReducer 25
AppReducer • AppStateͱAppAction͔ΒAppStateΛܭࢉ͢ΔΫϥε • Sealed ClassΛͬͨ߹else͕ෆཁʹͳΔ object AppReducer : ReducerType<AppState,
AppAction> { override fun reduce(state: AppState, action: AppAction): AppState { return when (action) { is AppAction.RefreshTodos -> { state.copy(todos = action.todos) } is AppAction.AddTodo -> { state.copy(todos = state.todos.plus(action.todo)) } } } } 26
AppStore 27
AppStore • AppStateͷอ࣋ɾมߋɾ௨Λߦ͏Ϋϥε class AppStore( private val initialState: AppState =
AppState() ) : StoreType<AppState> { private val state = BehaviorRelay.createDefault(initialState) override fun dispatch(action: ActionType) { state.value?.let { currentState -> val nextState = AppReducer.reduce(currentState, action as AppAction) state.accept(nextState) } } override fun observable(): Observable<AppState> { return state.observeOn(AndroidSchedulers.mainThread()) } } 28
AppStore • AppStateͷอ࣋ɾมߋɾ௨Λߦ͏Ϋϥε class AppStore( private val initialState: AppState =
AppState() ) : StoreType<AppState> { private val state = BehaviorRelay.createDefault(initialState) override fun dispatch(action: ActionType) { state.value?.let { currentState -> val nextState = AppReducer.reduce(currentState, action as AppAction) state.accept(nextState) } } override fun observable(): Observable<AppState> { return state.observeOn(AndroidSchedulers.mainThread()) } } 29 ॳظͷड͚औΓ
AppStore • AppStateͷอ࣋ɾมߋɾ௨Λߦ͏Ϋϥε class AppStore( private val initialState: AppState =
AppState() ) : StoreType<AppState> { private val state = BehaviorRelay.createDefault(initialState) override fun dispatch(action: ActionType) { state.value?.let { currentState -> val nextState = AppReducer.reduce(currentState, action as AppAction) state.accept(nextState) } } override fun observable(): Observable<AppState> { return state.observeOn(AndroidSchedulers.mainThread()) } } 30 ঢ়ଶͷอ࣋
AppStore • AppStateͷอ࣋ɾมߋɾ௨Λߦ͏Ϋϥε class AppStore( private val initialState: AppState =
AppState() ) : StoreType<AppState> { private val state = BehaviorRelay.createDefault(initialState) override fun dispatch(action: ActionType) { state.value?.let { currentState -> val nextState = AppReducer.reduce(currentState, action as AppAction) state.accept(nextState) } } override fun observable(): Observable<AppState> { return state.observeOn(AndroidSchedulers.mainThread()) } } 31 ঢ়ଶͷมߋ
AppStore • AppStateͷอ࣋ɾมߋɾ௨Λߦ͏Ϋϥε class AppStore( private val initialState: AppState =
AppState() ) : StoreType<AppState> { private val state = BehaviorRelay.createDefault(initialState) override fun dispatch(action: ActionType) { state.value?.let { currentState -> val nextState = AppReducer.reduce(currentState, action as AppAction) state.accept(nextState) } } override fun observable(): Observable<AppState> { return state.observeOn(AndroidSchedulers.mainThread()) } } 32 ঢ়ଶͷ௨
View 33
View • AppStateΛߪಡ͠ɺTodoҰཡΛඳը͢ΔΫϥε val adapter = TodoAdapter() binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter getAppStore().observable() .subscribeBy { state -> adapter.setTodos(state.todos) adapter.notifyDataSetChanged() } .addTo(disposables) 34
Ϣʔεέʔε • TodoҰཡ • TodoՃ 35
View → AppAction 36
View → AppAction • FloatingActionButtonͷԡԼ࣌ʹAppActionΛੜ͢Δ • ࠓճTodoͷՃΛߦ͏ͷͰɺAppAction.AddTodoΛੜ͢Δ binding.floatingActionButton .setOnClickListener {
val todo = Todo(" NewTask!") val action = AppAction.AddTodo(todo) } 37
AppAction → AppStore 38
AppAction → AppStore • AppActionΛAppStoreʹ͢͜ͱͰঢ়ଶมߋΛߦ͏ val todo = Todo(" NewTask!")
val action = AppAction.AddTodo(todo) getAppStore().dispatch(action) 39
AppStore → AppReducer 40
AppStore → AppReducer • AppStoreAppReducerΛར༻ͯ࣍͠ͷAppStateΛܭࢉ͢Δ class AppStore( private val initialState:
AppState = AppState() ) : StoreType<AppState> { private val state = BehaviorRelay.createDefault(initialState) override fun dispatch(action: ActionType) { state.value?.let { currentState -> val nextState = AppReducer.reduce(currentState, action as AppAction) state.accept(nextState) } } } 41
AppStore → AppReducer • AppStoreAppReducerΛར༻ͯ࣍͠ͷAppStateΛܭࢉ͢Δ class AppStore( private val initialState:
AppState = AppState() ) : StoreType<AppState> { private val state = BehaviorRelay.createDefault(initialState) override fun dispatch(action: ActionType) { state.value?.let { currentState -> val nextState = AppReducer.reduce(currentState, action as AppAction) state.accept(nextState) } } } 42 Actionͷड͚औΓ
AppStore → AppReducer • AppStoreAppReducerΛར༻ͯ࣍͠ͷAppStateΛܭࢉ͢Δ class AppStore( private val initialState:
AppState = AppState() ) : StoreType<AppState> { private val state = BehaviorRelay.createDefault(initialState) override fun dispatch(action: ActionType) { state.value?.let { currentState -> val nextState = AppReducer.reduce(currentState, action as AppAction) state.accept(nextState) } } } 43 ঢ়ଶͷมߋ
AppStore → AppState 44
AppStore → AppState • AppStoreঢ়ଶ͕มߋ͞ΕΔʹAppStateΛ௨͢Δ • AppStateViewʹߪಡ͞ΕΔͷͰϝΠϯεϨουͰ௨͢Δ class AppStore( private
val initialState: AppState = AppState() ) : StoreType<AppState> { private val state = BehaviorRelay.createDefault(initialState) override fun observable(): Observable<AppState> { return state.observeOn(AndroidSchedulers.mainThread()) } } 45
AppStore → AppState • AppStoreঢ়ଶ͕มߋ͞ΕΔʹAppStateΛ௨͢Δ • AppStateViewʹߪಡ͞ΕΔͷͰϝΠϯεϨουͰ௨͢Δ class AppStore( private
val initialState: AppState = AppState() ) : StoreType<AppState> { private val state = BehaviorRelay.createDefault(initialState) override fun observable(): Observable<AppState> { return state.observeOn(AndroidSchedulers.mainThread()) } } 46 ঢ়ଶͷมߋ௨
AppState → View 47
AppState → View • AppStateΛߪಡ͠ɺঢ়ଶมߋΛड͚औΔͱը໘Λߋ৽͢Δ val adapter = TodoAdapter() binding.recyclerView.layoutManager
= LinearLayoutManager(this) binding.recyclerView.adapter = adapter getAppStore().observable() .subscribeBy { state -> adapter.setTodos(state.todos) adapter.notifyDataSetChanged() } .addTo(disposables) 48
Ԡ༻ฤ 49
Ԡ༻ฤ • ࣮ࡍͷϓϩμΫτͰɺߟྀ͖͢͜ͱ͕ͨ͘͞Μ͢Δ • ඇಉظॲཧ • ը໘ભҠ • ঢ়ଶมߋͷෛՙ •
ςετίʔυ 50
ඇಉظॲཧ • Reduxঢ়ଶཧʹಛԽ͓ͯ͠ΓɺඇಉظॲཧΛࡹ͘ΈΛ࣋ͨͳ͍ • ReduxͷελϯεReducerҎલͰඇಉظॲཧΛࡹ͘͜ͱ • ͦͷݱΕͱͯ͠ɺReducerҎ߱ඞͣಉظॲཧͰهड़͢Δඞཁ͕͋Δ • ࣮ࡍඇಉظॲཧඞਢͰ͋ΓɺMiddlewareΛ༻͍࣮͕ͨओྲྀ •
redux-thunk • redux-saga • etc… 51
Middleware • StoreͰͷঢ়ଶมߋલޙʹॲཧΛڬΉͨΊͷػߏ • Middlewareͷ࣮ྫ • ϩΪϯάॲཧ • ඇಉظॲཧ •
etc… • ෳͷMiddlewareΛઃఆ͢Δ͜ͱ͕Մೳ 52
53
LoggerMiddleware • ϩάग़ྗMiddlewareͷ࠷୯७ͳར༻ྫ • Dispatch͞ΕͨActionΛஞҰϩάʹग़ྗ͢Δ • ඞཁʹԠͯ͡StateΛग़ྗ͢Δ͜ͱՄೳ class LoggerMiddleware :
MiddlewareType { override fun before(state: StateType, action: ActionType): Single<ActionType> { Log.d("ReduxKit", "Before dispatching: ${action::class.java.simpleName}") return Single.just(action) } override fun after(state: StateType, action: ActionType): Single<ActionType> { Log.d("ReduxKit", "After dispatching: ${action::class.java.simpleName}") return Single.just(action) } } 54
ThunkMiddleware • ඇಉظॲཧΛߦ͏ࡍͷ࠷ҰൠతͳΞϓϩʔν 55
ThunkMiddleware interface AsyncActionType : ActionType { fun execute(dispatcher: Dispatcher): Single<ActionType>
} class ThunkMiddleware( private val dispatcher: Dispatcher ) : MiddlewareType { override fun before(state: StateType, action: ActionType): Single<ActionType> { return if (action is AsyncActionType) { action.execute(dispatcher) } else { Single.just(action) } } override fun after(state: StateType, action: ActionType): Single<ActionType> { return Single.just(action) } } 56
ThunkMiddleware interface AsyncActionType : ActionType { fun execute(dispatcher: Dispatcher): Single<ActionType>
} class ThunkMiddleware( private val dispatcher: Dispatcher ) : MiddlewareType { override fun before(state: StateType, action: ActionType): Single<ActionType> { return if (action is AsyncActionType) { action.execute(dispatcher) } else { Single.just(action) } } override fun after(state: StateType, action: ActionType): Single<ActionType> { return Single.just(action) } } 57 ඇಉظॲཧ༻ͷInterface
ThunkMiddleware interface AsyncActionType : ActionType { fun execute(dispatcher: Dispatcher): Single<ActionType>
} class ThunkMiddleware( private val dispatcher: Dispatcher ) : MiddlewareType { override fun before(state: StateType, action: ActionType): Single<ActionType> { return if (action is AsyncActionType) { action.execute(dispatcher) } else { Single.just(action) } } override fun after(state: StateType, action: ActionType): Single<ActionType> { return Single.just(action) } } 58 ඇಉظॲཧͷ߹͚࣮ͩߦ͢Δ
ThunkMiddleware interface AsyncActionType : ActionType { fun execute(dispatcher: Dispatcher): Single<ActionType>
} class ThunkMiddleware( private val dispatcher: Dispatcher ) : MiddlewareType { override fun before(state: StateType, action: ActionType): Single<ActionType> { return if (action is AsyncActionType) { action.execute(dispatcher) } else { Single.just(action) } } override fun after(state: StateType, action: ActionType): Single<ActionType> { return Single.just(action) } } 59 Dispatcherͱʁ
Dispatcher • ඇಉظʹॲཧΛߦ͏ؒʹผͷActionΛൃߦ͢ΔͨΊͷΈ • ྫ͑ɺαʔόʔ͔ΒσʔλΛऔಘ͍ͯ͠ΔؒʹϩʔσΟϯά Λදࣔ͢Δ 60
61
ը໘ભҠ • Reduxঢ়ଶཧʹಛԽ͍ͯ͠Δ • ͦͦը໘ભҠঢ়ଶͳͷ͔ʁ • BooleanͰແཧΓঢ়ଶͱͯ͠දݱͰ͖ͳ͘ͳ͍ • Ұ࣌తͳͷͳͷͰঢ়ଶͱͯ͠දݱ͠ʹ͍͘ •
ઈରతͳਖ਼ղͳ͍͕ɺReduxͱผʹ࣮͢Δͷ͕ແ 62
ঢ়ଶมߋͷෛՙ • ReduxͰΞϓϦશମͷঢ়ଶΛ1ͭͷStateͰදݱ͢Δ • Stateͷઃܭʹ໌֬ͳࢦଘࡏ͠ͳ͍ • ୯७ͳΞϓϦͷ߹ɺը໘୯ҐͰStateΛఆٛ͢Εྑ͍ • ෳࡶͳΞϓϦͷ߹ɺը໘୯ҐͰStateΛఆٛ͢Δͱσʔλͷॏ ෳ͕ͱͳΔ
63
σʔλॏෳ • TodoΞϓϦʹػೳΛՃ͢Δ߹Λߟ͑Δ • Todoͷىථऀͱ͍͏֓೦ΛՃ͢Δ • શͯͷTodo͕දࣔ͞ΕΔը໘Λ࣮͢Δ • ࣗͷTodo͚͕ͩදࣔ͞ΕΔը໘Λ࣮͢Δ 64
65
σʔλͷਖ਼نԽ • RDBͷΑ͏ʹσʔλΛਖ਼نԽͯ͠ΈΔ 66
67
68
αϯϓϧ • GitHubΫϥΠΞϯτ • ݕࡧը໘ • ελʔҰཡը໘ • ٕज़ཁૉ •
ඇಉظॲཧ • σʔλͷਖ਼نԽ 69 ݕࡧը໘ ελʔҰཡը໘
શମ૾ 70
AppState 71
AppState • ΞϓϦશମͷঢ়ଶΛදݱ͢ΔΫϥε data class AppState( val domain: DomainState =
DomainState(), val search: SearchStoreState = SearchStoreState(), val star: StarStoreState = StarStoreState() ) : StateType { fun toSearchViewState(): SearchViewState fun toStarViewState(): StarViewState } 72
AppState • ΞϓϦશମͷঢ়ଶΛදݱ͢ΔΫϥε data class AppState( val domain: DomainState =
DomainState(), val search: SearchStoreState = SearchStoreState(), val star: StarStoreState = StarStoreState() ) : StateType { fun toSearchViewState(): SearchViewState fun toStarViewState(): StarViewState } 73 υϝΠϯΤϯςΟςΟΛදݱ͢ΔΫϥε
AppState • ΞϓϦશମͷঢ়ଶΛදݱ͢ΔΫϥε data class AppState( val domain: DomainState =
DomainState(), val search: SearchStoreState = SearchStoreState(), val star: StarStoreState = StarStoreState() ) : StateType { fun toSearchViewState(): SearchViewState fun toStarViewState(): StarViewState } 74 ݕࡧը໘ͷঢ়ଶΛදݱ͢ΔΫϥε
AppState • ΞϓϦશମͷঢ়ଶΛදݱ͢ΔΫϥε data class AppState( val domain: DomainState =
DomainState(), val search: SearchStoreState = SearchStoreState(), val star: StarStoreState = StarStoreState() ) : StateType { fun toSearchViewState(): SearchViewState fun toStarViewState(): StarViewState } 75 ελʔҰཡը໘ͷঢ়ଶΛදݱ͢ΔΫϥε
76
AppState data class AppState( val domain: DomainState = DomainState(), val
search: SearchStoreState = SearchStoreState(), val star: StarStoreState = StarStoreState() ) : StateType { fun toSearchViewState(): SearchViewState { return SearchViewState( isLoading = search.isLoading, repos = search.repos.map { domain.findRepoById(it.id) } ) } fun toStarViewState(): StarViewState { return StarViewState( isLoading = star.isLoading, repos = star.repos.map { domain.findRepoById(it.id) } ) } } 77
AppAction 78
AppAction • ঢ়ଶมߋΛදݱ͢ΔΫϥε sealed class AppAction : ActionType { sealed
class DomainAction : AppAction() sealed class SearchAction : AppAction() sealed class StarAction : AppAction() } 79
AppAction • ঢ়ଶมߋΛදݱ͢ΔΫϥε sealed class AppAction : ActionType { sealed
class DomainAction : AppAction() sealed class SearchAction : AppAction() sealed class StarAction : AppAction() } 80 DomainStateͷঢ়ଶมߋΛදݱ͢ΔΫϥε
AppAction • ঢ়ଶมߋΛදݱ͢ΔΫϥε sealed class AppAction : ActionType { sealed
class DomainAction : AppAction() sealed class SearchAction : AppAction() sealed class StarAction : AppAction() } 81 SearchStateͷঢ়ଶมߋΛදݱ͢ΔΫϥε
AppAction • ঢ়ଶมߋΛදݱ͢ΔΫϥε sealed class AppAction : ActionType { sealed
class DomainAction : AppAction() sealed class SearchAction : AppAction() sealed class StarAction : AppAction() } 82 StarStateͷঢ়ଶมߋΛදݱ͢ΔΫϥε
AppReducer 83
AppReducer • AppStateͱAppAction͔ΒAppStateΛܭࢉ͢ΔΫϥε object AppReducer : ReducerType<AppState, AppAction> { override
fun reduce(state: AppState, action: AppAction): AppState { return when (action) { is AppAction.DomainAction -> { state.copy(domain = DomainReducer.reduce(state.domain, action)) } is AppAction.SearchAction -> { state.copy(search = SearchReducer.reduce(state.search, action)) } is AppAction.StarAction -> { state.copy(star = StarReducer.reduce(state.star, action)) } } } } 84
AppReducer • AppStateͱAppAction͔ΒAppStateΛܭࢉ͢ΔΫϥε object AppReducer : ReducerType<AppState, AppAction> { override
fun reduce(state: AppState, action: AppAction): AppState { return when (action) { is AppAction.DomainAction -> { state.copy(domain = DomainReducer.reduce(state.domain, action)) } is AppAction.SearchAction -> { state.copy(search = SearchReducer.reduce(state.search, action)) } is AppAction.StarAction -> { state.copy(star = StarReducer.reduce(state.star, action)) } } } } 85 DomainReducerΛ༻ͯ͠DomainStateΛܭࢉ
AppReducer • AppStateͱAppAction͔ΒAppStateΛܭࢉ͢ΔΫϥε object AppReducer : ReducerType<AppState, AppAction> { override
fun reduce(state: AppState, action: AppAction): AppState { return when (action) { is AppAction.DomainAction -> { state.copy(domain = DomainReducer.reduce(state.domain, action)) } is AppAction.SearchAction -> { state.copy(search = SearchReducer.reduce(state.search, action)) } is AppAction.StarAction -> { state.copy(star = StarReducer.reduce(state.star, action)) } } } } 86 SearchReducerΛ༻ͯ͠SearchStateΛܭࢉ
AppReducer • AppStateͱAppAction͔ΒAppStateΛܭࢉ͢ΔΫϥε object AppReducer : ReducerType<AppState, AppAction> { override
fun reduce(state: AppState, action: AppAction): AppState { return when (action) { is AppAction.DomainAction -> { state.copy(domain = DomainReducer.reduce(state.domain, action)) } is AppAction.SearchAction -> { state.copy(search = SearchReducer.reduce(state.search, action)) } is AppAction.StarAction -> { state.copy(star = StarReducer.reduce(state.star, action)) } } } } 87 StarReducerΛ༻ͯ͠StarStateΛܭࢉ
AppStore 88
AppStore • AppStateͷอ࣋ɾมߋɾ௨ • MiddlewareΛར༻ͨ͠ϩάग़ྗɾඇಉظॲཧ 89
AppStore 90
override fun dispatch(action: ActionType) { state.value?.let { currentState -> Single.just(action)
.flatMap { originalAction -> var stream = Single.just(originalAction) middlewares.forEach { middleware -> stream = stream.flatMap { currentAction -> middleware.before(currentState, currentAction) } } return@flatMap stream } .doOnSuccess { update(it) } .flatMap { originalAction -> var stream = Single.just(originalAction) middlewares.forEach { middleware -> stream = stream.flatMap { currentAction -> middleware.after(currentState, currentAction) } } return@flatMap stream } .subscribe() } } 91
override fun dispatch(action: ActionType) { state.value?.let { currentState -> Single.just(action)
.flatMap { originalAction -> var stream = Single.just(originalAction) middlewares.forEach { middleware -> stream = stream.flatMap { currentAction -> middleware.before(currentState, currentAction) } } return@flatMap stream } .doOnSuccess { update(it) } .flatMap { originalAction -> var stream = Single.just(originalAction) middlewares.forEach { middleware -> stream = stream.flatMap { currentAction -> middleware.after(currentState, currentAction) } } return@flatMap stream } .subscribe() } } 92 Dispatch͞ΕͨActionΛىʹॲཧΛ։࢝
override fun dispatch(action: ActionType) { state.value?.let { currentState -> Single.just(action)
.flatMap { originalAction -> var stream = Single.just(originalAction) middlewares.forEach { middleware -> stream = stream.flatMap { currentAction -> middleware.before(currentState, currentAction) } } return@flatMap stream } .doOnSuccess { update(it) } .flatMap { originalAction -> var stream = Single.just(originalAction) middlewares.forEach { middleware -> stream = stream.flatMap { currentAction -> middleware.after(currentState, currentAction) } } return@flatMap stream } .subscribe() } } 93 ࣄલॲཧ༻ͷMiddlewareΛ࿈݁
override fun dispatch(action: ActionType) { state.value?.let { currentState -> Single.just(action)
.flatMap { originalAction -> var stream = Single.just(originalAction) middlewares.forEach { middleware -> stream = stream.flatMap { currentAction -> middleware.before(currentState, currentAction) } } return@flatMap stream } .doOnSuccess { update(it) } .flatMap { originalAction -> var stream = Single.just(originalAction) middlewares.forEach { middleware -> stream = stream.flatMap { currentAction -> middleware.after(currentState, currentAction) } } return@flatMap stream } .subscribe() } } 94 Storeͷঢ়ଶΛߋ৽
override fun dispatch(action: ActionType) { state.value?.let { currentState -> Single.just(action)
.flatMap { originalAction -> var stream = Single.just(originalAction) middlewares.forEach { middleware -> stream = stream.flatMap { currentAction -> middleware.before(currentState, currentAction) } } return@flatMap stream } .doOnSuccess { update(it) } .flatMap { originalAction -> var stream = Single.just(originalAction) middlewares.forEach { middleware -> stream = stream.flatMap { currentAction -> middleware.after(currentState, currentAction) } } return@flatMap stream } .subscribe() } } 95 ࣄޙॲཧ༻ͷMiddlewareΛ࿈݁
Ϣʔεέʔεຖͷ࣮ • ϦϙδτϦͷݕࡧॲཧ • ݕࡧը໘ʹೖྗ͞ΕͨΫΤϦΛͱʹݕࡧΛ࣮ߦ • ݕࡧ݁ՌΛҰཡͰදࣔ • ϦϙδτϦͷελʔॲཧ •
ϦϙδτϦͷݕࡧ݁Ռ͔ΒελʔॲཧΛ࣮ߦ • ελʔॲཧྃޙʹɺݕࡧը໘ͱελʔҰཡը໘ʹө 96
ϦϙδτϦͷݕࡧॲཧ • ॲཧͷྲྀΕ • ݕࡧॲཧΛඇಉظͰ࣮ߦ͢Δ • ݕࡧॲཧͷ։࢝࣌ʹϩʔσΟϯάΛදࣔ͢Δ • ݕࡧॲཧͷऴྃ࣌ʹϩʔσΟϯάΛඇදࣔʹ͢Δ •
ݕࡧ݁ՌΛݕࡧը໘ʹө͢Δ 97
class SearchActionCreator @Inject constructor( private val repository: GitHubRepository ) {
fun fetchSearchRepositories(query: String): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single<ActionType> { return repository.searchRepositoriesByQuery(query) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { dispatcher.dispatch(AppAction.SearchAction.RefreshLoading(true)) } .doOnEvent { _, _ -> dispatcher.dispatch(AppAction.SearchAction.RefreshLoading(false)) } .doOnSuccess { repos -> dispatcher.dispatch(AppAction.DomainAction.PutRepos(repos)) } .map { repos -> AppAction.SearchAction.RefreshRepos(repos) } } } } } 98
class SearchActionCreator @Inject constructor( private val repository: GitHubRepository ) {
fun fetchSearchRepositories(query: String): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single<ActionType> { return repository.searchRepositoriesByQuery(query) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { dispatcher.dispatch(AppAction.SearchAction.RefreshLoading(true)) } .doOnEvent { _, _ -> dispatcher.dispatch(AppAction.SearchAction.RefreshLoading(false)) } .doOnSuccess { repos -> dispatcher.dispatch(AppAction.DomainAction.PutRepos(repos)) } .map { repos -> AppAction.SearchAction.RefreshRepos(repos) } } } } } 99 ݕࡧॲཧΛ࣮ߦ
class SearchActionCreator @Inject constructor( private val repository: GitHubRepository ) {
fun fetchSearchRepositories(query: String): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single<ActionType> { return repository.searchRepositoriesByQuery(query) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { dispatcher.dispatch(AppAction.SearchAction.RefreshLoading(true)) } .doOnEvent { _, _ -> dispatcher.dispatch(AppAction.SearchAction.RefreshLoading(false)) } .doOnSuccess { repos -> dispatcher.dispatch(AppAction.DomainAction.PutRepos(repos)) } .map { repos -> AppAction.SearchAction.RefreshRepos(repos) } } } } } 100 ݕࡧॲཧͷ։࢝࣌ʹϩʔσΟϯάΛදࣔ
class SearchActionCreator @Inject constructor( private val repository: GitHubRepository ) {
fun fetchSearchRepositories(query: String): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single<ActionType> { return repository.searchRepositoriesByQuery(query) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { dispatcher.dispatch(AppAction.SearchAction.RefreshLoading(true)) } .doOnEvent { _, _ -> dispatcher.dispatch(AppAction.SearchAction.RefreshLoading(false)) } .doOnSuccess { repos -> dispatcher.dispatch(AppAction.DomainAction.PutRepos(repos)) } .map { repos -> AppAction.SearchAction.RefreshRepos(repos) } } } } } 101 ݕࡧॲཧͷऴྃ࣌ʹϩʔσΟϯάΛඇදࣔ
class SearchActionCreator @Inject constructor( private val repository: GitHubRepository ) {
fun fetchSearchRepositories(query: String): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single<ActionType> { return repository.searchRepositoriesByQuery(query) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { dispatcher.dispatch(AppAction.SearchAction.RefreshLoading(true)) } .doOnEvent { _, _ -> dispatcher.dispatch(AppAction.SearchAction.RefreshLoading(false)) } .doOnSuccess { repos -> dispatcher.dispatch(AppAction.DomainAction.PutRepos(repos)) } .map { repos -> AppAction.SearchAction.RefreshRepos(repos) } } } } } 102 DomainStateΛมߋ
class SearchActionCreator @Inject constructor( private val repository: GitHubRepository ) {
fun fetchSearchRepositories(query: String): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single<ActionType> { return repository.searchRepositoriesByQuery(query) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { dispatcher.dispatch(AppAction.SearchAction.RefreshLoading(true)) } .doOnEvent { _, _ -> dispatcher.dispatch(AppAction.SearchAction.RefreshLoading(false)) } .doOnSuccess { repos -> dispatcher.dispatch(AppAction.DomainAction.PutRepos(repos)) } .map { repos -> AppAction.SearchAction.RefreshRepos(repos) } } } } } 103 ݕࡧ݁ՌΛը໘ʹө
ϦϙδτϦͷελʔॲཧ • ॲཧͷྲྀΕ • ελʔॲཧΛඇಉظͰ࣮ߦ͢Δ • ελʔҰཡը໘ʹϦϙδτϦΛՃ͢Δ 104
class StarActionCreator @Inject constructor( private val repository: GitHubRepository ) {
fun starRepo(repo: Repo): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single<ActionType> { return repository.starRepo(repo) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSuccess { repo -> dispatcher.dispatch(AppAction.DomainAction.StarRepo(repo)) } .map { repo -> AppAction.StarAction.AddRepo(repo) } } } } } 105
class StarActionCreator @Inject constructor( private val repository: GitHubRepository ) {
fun starRepo(repo: Repo): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single<ActionType> { return repository.starRepo(repo) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSuccess { repo -> dispatcher.dispatch(AppAction.DomainAction.StarRepo(repo)) } .map { repo -> AppAction.StarAction.AddRepo(repo) } } } } } 106 ελʔॲཧΛ࣮ߦ
class StarActionCreator @Inject constructor( private val repository: GitHubRepository ) {
fun starRepo(repo: Repo): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single<ActionType> { return repository.starRepo(repo) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSuccess { repo -> dispatcher.dispatch(AppAction.DomainAction.StarRepo(repo)) } .map { repo -> AppAction.StarAction.AddRepo(repo) } } } } } 107 ελʔॲཧͷྃޙʹDomainStateΛมߋ
class StarActionCreator @Inject constructor( private val repository: GitHubRepository ) {
fun starRepo(repo: Repo): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single<ActionType> { return repository.starRepo(repo) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSuccess { repo -> dispatcher.dispatch(AppAction.DomainAction.StarRepo(repo)) } .map { repo -> AppAction.StarAction.AddRepo(repo) } } } } } 108 ελʔҰཡը໘ʹϦϙδτϦΛՃ
ςετίʔυ • AppState/AppAction • ೖΕΫϥεͳͷͰςετෆཁ • AppReducer • ঢ়ଶมߋͷܭࢉ͕ਖ਼͘͠ߦΘΕΔ͔ΛνΣοΫ͢Δ •
७ਮͳؔͱ࣮͍ͯͯ͠͠ΔͷͰඇৗʹςετָ͕ • AppStore • ঢ়ଶͷอ࣋ɾมߋɾ௨͕ਖ਼͘͠ߦΘΕΔ͔ΛνΣοΫ͢Δ • RxJava͕བྷΉ෦TestObserverΛར༻ͯ͠ςετ͢Δ 109
AppReducer • ঢ়ଶมߋͷܭࢉ͕ਖ਼͘͠ߦΘΕΔ͔ΛνΣοΫ͢Δ var state = AppState() assert(!state.search.isLoading) val action
= AppAction.SearchAction.RefreshLoading(isLoading = true) state = AppReducer.reduce(state, action) assert(state.search.isLoading) 110
AppStore • ঢ়ଶͷอ࣋ɾมߋɾ௨͕ਖ਼͘͠ߦΘΕΔ͔ΛνΣοΫ͢Δ val state = AppState() val store =
AppStore(state) val observer = TestObserver<AppState>() store.observable().subscribe(observer) observer.assertValueCount(1) observer.assertValueAt(0, state) val action = AppAction.SearchAction.RefreshLoading(true) store.dispatch(action) observer.assertValueCount(2) observer.assertValueAt(1, state.copy(search = state.search.copy(isLoading = true))) 111
͓ΘΓʹ 112
ϝϦοτɾσϝϦοτ • ϝϦοτ • ୯ํσʔλϑϩʔʹΑͬͯίʔυ͕͍͘͢ͳΔ • ঢ়ଶཧͷ࣮͕ΞϓϦશମͰ౷Ұ͞ΕΔ • AndroidͷϥΠϑαΠΫϧʹࠨӈ͞Εͳ͘ͳΔ •
σϝϦοτ • ReduxͷࢥΛཧղ͢Δ·Ͱ։ൃεϐʔυ͕μϯ • ίʔυྔ͕૿͑ɺσʔλਖ਼نԽ·ͰؚΊΔͱ࣮͕େม 113
ଞͷΞʔΩςΫνϟͱͷؔੑ 114 Presentation Model MVP ̋ ✕ MVVM ̋ ✕
Flux / Redux ̋ ✕ AAC ̋ ✕ Layered Architecture ✕ ̋
PairsͷΞʔΩςΫνϟ • Redux • MVVM • Layered Architecture 115
·ͱΊ • AndroidΞϓϦ։ൃͰঢ়ଶཧ͕·͍͠ • ը໘Λލ͍ͩঢ়ଶͷಉظ • ϥΠϑαΠΫϧͱͷ͖߹͍ํ • ReduxΛ࠾༻͢Δ͜ͱͰঢ়ଶཧʹ·ͭΘΔ͕ղফՄೳ •
ঢ়ଶΛதԝूݖతʹཧ • ϥΠϑαΠΫϧͱঢ়ଶཧΛ 116
·ͱΊ • جૅฤ • Stateɿঢ়ଶΛදݱ͢Δͷ • Actionɿঢ়ଶͷมߋ༰Λදݱ͢Δͷ • Reducerɿ࣍ͷঢ়ଶΛܭࢉ͢Δͷ •
Storeɿঢ়ଶอ࣋ͱมߋΛ୲͢Δͷ • Ԡ༻ฤ • ඇಉظॲཧɿMiddlewareͰͷ࣮͕ओྲྀ • ը໘ભҠɿReduxͱผͰ࣮͢Δ • σʔλͷਖ਼نԽɿσʔλͷมߋෛՙΛԼ͛Δ • ςετίʔυɿReducer؆୯ɺStoreTestObserverΛ͏ 117
118 Credit: NASA Earth Observatory/NOAA NGDC Thank you :)