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
850
droidkaigi-2018
yuyakaido
4
6.2k
navitime-eureka-1
yuyakaido
0
110
droidkaigi-2017
yuyakaido
11
8.4k
retty-tech-cafe-8
yuyakaido
0
210
mti-eureka-tech-beer
yuyakaido
0
510
potatotips-33
yuyakaido
2
680
Other Decks in Programming
See All in Programming
AIとGame Jamで、ゲームを完成させた話
takahirosaeki
0
120
型解析で実現する Go の言語内 DSL / Conference に Go! タイムテーブルの歩き方 for Gophers
mazrean
0
140
ゲームコントローラやキーボードのファームウェアをSwiftで書く
kishikawakatsumi
1
120
AIは賢い。でも実行環境は? CLIおじさんがAI時代に伝えたいこと ~ CLIおじさんがAI時代に伝えたいこと ~
curekoshimizu
1
230
AIを上手に使っていこうとしたら越境せざるを得なくなった話 〜実践1年で見えた境界を越えなければならない理由と進め方〜 / Crossing borders with AI
tomoyakitaura
4
1.1k
App Storeの外へ──日本のiOSサイドローディング入門 for iOSDC Japan 2026
yuukiw00w
0
160
GKE アップグレード前に知っておきたい Blue/Green と PDB の関係
stkk
0
160
App Intentsのビルドプロセスを支える技術
kntkymt
0
200
Seeing Through Serverless: Observability for AWS Lambda with ADOT and CloudWatch Application Signals
seike460
PRO
1
130
typoなんかねぇよ
raspython3
0
690
業務時間外もAIに働いてもらう話
colorful12
3
10k
マイコン向けの軽量Ruby「PicoRuby」で各種デバイスを制御するネイティブアプリの実現手法
bash0c7
0
320
Featured
See All Featured
Typedesign – Prime Four
hannesfritz
42
3.2k
How to train your dragon (web standard)
notwaldorf
97
6.8k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
249
1.3M
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
330
Fashionably flexible responsive web design (full day workshop)
malarkey
408
67k
Odyssey Design
rkendrick25
PRO
2
800
A Tale of Four Properties
chriscoyier
163
24k
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
560
Beyond borders and beyond the search box: How to win the global "messy middle" with AI-driven SEO
davidcarrasco
3
240
Google's AI Overviews - The New Search
badams
0
1.6k
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
68
57k
Lessons Learnt from Crawling 1000+ Websites
charlesmeaden
PRO
1
1.6k
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 :)