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
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
phperだってパンチカードでコーディングしたい
o0h
PRO
0
310
Rでドレミ/Do-Re-Mi_with_R
florets1
0
130
Workers Cache を知る
syumai
0
360
JAWS-UG 東京支部が始める、JAWS-UG支部コラボ / JAWS-UG lunchtime LT Collaboration
y0hgi
0
260
Atomic Design, Enforced: Scaling Mobile Design Systems (next.app devCon / droidCon Berlin 2026)
steliosf
PRO
0
170
KiroのSpecで「五目並べ」を作ってみる
satoshi256kbyte
1
370
Event is what happens
moro
2
470
Railsのためのパスキー実践入門 〜マルチテナントSaaSでの意思決定〜
utakaha
0
180
UnityでSystem.Net.WebSocketsなWebSocketサーバが動かないのでUnity Monoのコードを覗いてみた / about implementing websocket server with unity mono
drumath2237
1
590
ハーネス設計入門 〜 基礎知識の整理から実務へのステップアップ 〜
kinopeee
22
22k
個人開発者よ、Honoを使え(おじさんver)
tanahiro2010
2
250
SalesForceを内製化!? ~ HR事業を支える顧客管理基盤のインフラを大公開 ~
oku053
0
170
Featured
See All Featured
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
540
Git: the NoSQL Database
bkeepers
PRO
433
67k
Building Adaptive Systems
keathley
44
3.2k
AI Search: Where Are We & What Can We Do About It?
aleyda
0
8.1k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
300
How to Ace a Technical Interview
jacobian
280
24k
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
270
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
350
Paper Plane
katiecoart
PRO
5
54k
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
570
Fireside Chat
paigeccino
43
4.1k
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 :)