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
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
850
droidkaigi-2018
yuyakaido
4
6.2k
navitime-eureka-1
yuyakaido
0
110
droidkaigi-2017
yuyakaido
11
8.3k
retty-tech-cafe-8
yuyakaido
0
200
mti-eureka-tech-beer
yuyakaido
0
500
potatotips-33
yuyakaido
2
680
Other Decks in Programming
See All in Programming
AIエージェントの隔離技術の徹底比較
kawayu
0
460
権限チェックの一貫性を型で守る TypeScript による多層防御
mnch
4
1.1k
Swiftのレキシカルスコープ管理
kntkymt
0
210
LLM本来の能力を解き放つサンドボックス技術とAI民主化への適用
yukukotani
3
3.1k
Why Laravel apps break—Mastering the fundamentals to keep them maintainable
kentaroutakeda
1
340
「エンジニアインターン、どうやって取った?」準備のリアルを語るLT会 Progate BAR
akiomatic
0
120
AI駆動開発で崩れていくコードベースを立て直す
kyoko_nr_nr
1
440
Stage 3 Decorators でできること / できないこと / TSKaigi 2026
susisu
1
1.6k
軽量Java基盤の設計 DIコンテナに頼らない、長期保守と1秒起動の実現 JJUG CCC 2026 Spring
macha64
0
460
Oxlintのカスタムルールの現況
syumai
6
1k
Java × distroless で 軽量なコンテナイメージを / Java on Distroless
contour_gara
0
500
AI時代のUIはどこへ行く?その2!
yusukebe
19
6.7k
Featured
See All Featured
The B2B funnel & how to create a winning content strategy
katarinadahlin
PRO
1
380
Optimising Largest Contentful Paint
csswizardry
37
3.7k
Collaborative Software Design: How to facilitate domain modelling decisions
baasie
1
240
HDC tutorial
michielstock
2
690
The browser strikes back
jonoalderson
0
1.1k
WENDY [Excerpt]
tessaabrams
11
38k
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
1
280
Statistics for Hackers
jakevdp
799
230k
Claude Code のすすめ
schroneko
67
230k
Testing 201, or: Great Expectations
jmmastey
46
8.2k
What does AI have to do with Human Rights?
axbom
PRO
1
2.2k
Building an army of robots
kneath
306
46k
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 :)