Slide 1

Slide 1 text

Redux for Android yuyakaido DroidKaigi 2019

Slide 2

Slide 2 text

ࣗݾ঺հ • ւ౻༏໻ʢ͔͍Ͳ͏Ώ͏΍ʣ • גࣜձࣾΤ΢ϨΧ • AndroidΤϯδχΞ • DroidKaigi • 2016 - ςετ͓͡͞Μ • 2017 - RxJavaͷΤϥʔϋϯυϦϯά • 2018 - ϚϧνϩάΠϯͷ࣮૷ํ๏ • 2019 - Redux for Android 2 yuyakaido

Slide 3

Slide 3 text

Pairs Japan / Global • ೔ຊ࠷େͷϚονϯάαʔϏε • ୆࿷ɾؖࠃͰ΋αʔϏεల։ • ձһ਺ɿ1000ສਓ • Ϛονϯά਺ɿ5600ສ૊ • Χοϓϧ਺ɿ10ສ૊ 3

Slide 4

Slide 4 text

໨࣍ • ͸͡Ίʹ • AndroidΞϓϦ։ൃʹ͓͚Δ೰·͍͠໰୊ɺଞͷϓϥοτϑΥʔϜͰͷղܾࡦ • جૅฤ • Reduxͷશମ૾ͱߏ੒ཁૉ • αϯϓϧɿTODOΞϓϦ • Ԡ༻ฤ • ඇಉظॲཧɺը໘ભҠɺঢ়ଶมߋͷෛՙɺςετίʔυ • αϯϓϧɿGitHubΫϥΠΞϯτ • ͓ΘΓʹ • ReduxͷϝϦοτɾσϝϦοτ • ଞͷΞʔΩςΫνϟͱͷؔ܎ੑ • ·ͱΊ 4

Slide 5

Slide 5 text

͸͡Ίʹ 5

Slide 6

Slide 6 text

Android։ൃʹ͓͚Δ೰·͍͠໰୊ • ୺຤΍OSґଘͷڍಈ • iOS༏੎ͳ೔ຊࢢ৔ • ෳࡶͳঢ়ଶ؅ཧ • ը໘Λލ͍ͩঢ়ଶͷಉظ • ϥΠϑαΠΫϧͱͷ෇͖߹͍ํ 6

Slide 7

Slide 7 text

ը໘Λލ͍ͩঢ়ଶͷಉظ • ෳ਺ͷը໘ͰಉҰͷ৘ใ͕දࣔ͞Ε͍ͯΔ৔߹ɺ͋Δը໘Ͱߦ ΘΕͨঢ়ଶมߋΛଞͷը໘ʹ΋ಉظ͢Δඞཁ͕͋Δ • ඪ४APIΛ༻͍ͯঢ়ଶΛಉظ͢Δ৔߹͸ɺը໘ؒͰ૒ํ޲ʹσʔ λͷ΍ΓऔΓΛߦ͏ • Կ͕໰୊ʁ • ը໘͕૒ํ޲ʹґଘͯ͠Մಡੑ͕ѱ͍ 7

Slide 8

Slide 8 text

ϥΠϑαΠΫϧͱͷ෇͖߹͍ํ • ਺ଟ͘ͷϥΠϑαΠΫϧ͕ଘࡏ͠ɺϥΠϑαΠΫϧʹ߹Θͤͯঢ় ଶΛ؅ཧ͢Δඞཁ͕͋Δ • ը໘ճస΍ϝϞϦෆ଍ʹΑΔը໘ͷ࠶ੜ੒ͱ͍ͬͨΠϨΪϡϥʔ έʔε΋ߟྀ͢Δඞཁ͕͋Δ • Կ͕໰୊ʁ • ϥΠϑαΠΫϧͱঢ়ଶ؅ཧͱ͍͏ຊ࣭తʹ͸ແؔ܎ͷ΋ͷ͕ ಉډ͍ͯ͠Δ 8

Slide 9

Slide 9 text

ෳࡶͳঢ়ଶ؅ཧͷ໰୊఺ • ঢ়ଶΛಉظ͢ΔͨΊʹը໘͕૒ํ޲ʹґଘ͍ͯ͠Δ • ϥΠϑαΠΫϧͱঢ়ଶ؅ཧ͕ಉډ͍ͯ͠Δ 9

Slide 10

Slide 10 text

ଞͷϓϥοτϑΥʔϜͰͷղܾࡦ • ୯ํ޲σʔλϑϩʔͷߟ͑ํʹଇͬͯঢ়ଶ؅ཧΛߦ͏ͷ͕ओྲྀ • σʔλͷྲྀΕΛ୯ํ޲ʹ੍ݶ͢Δߟ͑ํ • ͜ΕʹΑͬͯঢ়ଶมߋʹ·ͭΘΔ໰୊ͷଟ͕͘ղফՄೳ • ୯ํ޲σʔλϑϩʔͷ࣮૷ྫͱͯ͠ɺFacebook͕FluxΛఏএ • ͦͷޙ΋ଟ͘ͷ࣮૷ྫ͕ొ৔͠ɺ࠷΋ϝδϟʔͳ΋ͷ͕Redux 10

Slide 11

Slide 11 text

Redux • ୯ํ޲σʔλϑϩʔΛϕʔεͱͨ͠ΞʔΩςΫνϟ • ঢ়ଶΛதԝͰҰׅ؅ཧ͠ɺը໘͸ͦΕΛඳը͢Δ͚ͩ • ReduxͰঢ়ଶ؅ཧͷ໰୊ΛղফͰ͖ΔͷͰ͸ͳ͍͔ʁ • ঢ়ଶΛதԝͰҰׅ؅ཧ͢Δ͜ͱͰը໘͕૒ํ޲ʹґଘ͠ͳ͍ • ঢ়ଶ؅ཧͱϥΠϑαΠΫϧΛ෼཭͢Δ͜ͱ͕Ͱ͖Δ 11

Slide 12

Slide 12 text

جૅฤ 12

Slide 13

Slide 13 text

Reduxͷߏ੒ཁૉ 13

Slide 14

Slide 14 text

State • ΞϓϦશମͷঢ়ଶΛදݱ͢Δ΋ͷ • ΞϓϦશମͷঢ়ଶΛ1ͭͷStateͰදݱ͢Δ • ෭࡞༻Λ๷ࢭ͢ΔͨΊʹಡΈऔΓઐ༻Ͱఆٛ͢Δͱྑ͍ 14

Slide 15

Slide 15 text

Action • ঢ়ଶͷมߋ಺༰Λදݱ͢Δ΋ͷ • ActionΛStoreʹ౉͢͜ͱͰঢ়ଶͷมߋΛߦ͏͜ͱ͕ग़དྷΔ 15

Slide 16

Slide 16 text

Reducer • StateͱAction͔ΒStateΛܭࢉ͢Δ΋ͷ • ܕͰදݱ͢Δͱɺ(State, Action) -> StateͱͳΔ • ෭࡞༻ͷͳ͍७ਮͳؔ਺ͱ࣮ͯ͠૷͢Δඞཁ͕͋Δ 16

Slide 17

Slide 17 text

Store • Stateͷอ࣋ɾมߋɾ௨஌Λߦ͏΋ͷ • ঢ়ଶมߋॲཧ͸ReducerΛ༻͍ͯߦ͏ • RxJavaΛར༻͢Δͱ࣮૷͕΍Γ΍͍͢ 17

Slide 18

Slide 18 text

αϯϓϧ • TodoΞϓϦ • TodoҰཡ • Todo௥Ճ • ٕज़ཁૉ • Reduxͷશମ૾ • ֤ཁૉͷ࣮૷ • ঢ়ଶมߋͷखॱ • ϦϙδτϦ • https://github.com/yuyakaido/ReduxKit 18

Slide 19

Slide 19 text

શମ૾ 19

Slide 20

Slide 20 text

Ϣʔεέʔε • TodoҰཡ • Todo௥Ճ 20

Slide 21

Slide 21 text

AppState 21

Slide 22

Slide 22 text

AppState • ΞϓϦશମͷঢ়ଶΛදݱ͢ΔΫϥε • Data Classͱͯ͠ఆٛ͢Δͱঢ়ଶมߋָ͕ data class AppState( val todos: List = emptyList() ) : StateType data class Todo( val title: String ) 22

Slide 23

Slide 23 text

AppAction 23

Slide 24

Slide 24 text

AppAction • ঢ়ଶมߋΛදݱ͢ΔΫϥε • Sealed ClassͰ࣮૷͢Δ͜ͱͰReducerΛγϯϓϧʹͰ͖Δ sealed class AppAction : ActionType { data class RefreshTodos(val todos: List) : AppAction() data class AddTodo(val todo: Todo) : AppAction() } 24

Slide 25

Slide 25 text

AppReducer 25

Slide 26

Slide 26 text

AppReducer • AppStateͱAppAction͔ΒAppStateΛܭࢉ͢ΔΫϥε • Sealed ClassΛ࢖ͬͨ৔߹͸else͕ෆཁʹͳΔ object AppReducer : ReducerType { 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

Slide 27

Slide 27 text

AppStore 27

Slide 28

Slide 28 text

AppStore • AppStateͷอ࣋ɾมߋɾ௨஌Λߦ͏Ϋϥε class AppStore( private val initialState: AppState = AppState() ) : StoreType { 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 { return state.observeOn(AndroidSchedulers.mainThread()) } } 28

Slide 29

Slide 29 text

AppStore • AppStateͷอ࣋ɾมߋɾ௨஌Λߦ͏Ϋϥε class AppStore( private val initialState: AppState = AppState() ) : StoreType { 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 { return state.observeOn(AndroidSchedulers.mainThread()) } } 29 ॳظ஋ͷड͚औΓ

Slide 30

Slide 30 text

AppStore • AppStateͷอ࣋ɾมߋɾ௨஌Λߦ͏Ϋϥε class AppStore( private val initialState: AppState = AppState() ) : StoreType { 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 { return state.observeOn(AndroidSchedulers.mainThread()) } } 30 ঢ়ଶͷอ࣋

Slide 31

Slide 31 text

AppStore • AppStateͷอ࣋ɾมߋɾ௨஌Λߦ͏Ϋϥε class AppStore( private val initialState: AppState = AppState() ) : StoreType { 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 { return state.observeOn(AndroidSchedulers.mainThread()) } } 31 ঢ়ଶͷมߋ

Slide 32

Slide 32 text

AppStore • AppStateͷอ࣋ɾมߋɾ௨஌Λߦ͏Ϋϥε class AppStore( private val initialState: AppState = AppState() ) : StoreType { 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 { return state.observeOn(AndroidSchedulers.mainThread()) } } 32 ঢ়ଶͷ௨஌

Slide 33

Slide 33 text

View 33

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Ϣʔεέʔε • TodoҰཡ • Todo௥Ճ 35

Slide 36

Slide 36 text

View → AppAction 36

Slide 37

Slide 37 text

View → AppAction • FloatingActionButtonͷԡԼ࣌ʹAppActionΛੜ੒͢Δ • ࠓճ͸Todoͷ௥ՃΛߦ͏ͷͰɺAppAction.AddTodoΛੜ੒͢Δ binding.floatingActionButton .setOnClickListener { val todo = Todo(" NewTask!") val action = AppAction.AddTodo(todo) } 37

Slide 38

Slide 38 text

AppAction → AppStore 38

Slide 39

Slide 39 text

AppAction → AppStore • AppActionΛAppStoreʹ౉͢͜ͱͰঢ়ଶมߋΛߦ͏ val todo = Todo(" NewTask!") val action = AppAction.AddTodo(todo) getAppStore().dispatch(action) 39

Slide 40

Slide 40 text

AppStore → AppReducer 40

Slide 41

Slide 41 text

AppStore → AppReducer • AppStore͸AppReducerΛར༻ͯ࣍͠ͷAppStateΛܭࢉ͢Δ class AppStore( private val initialState: AppState = AppState() ) : StoreType { 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

Slide 42

Slide 42 text

AppStore → AppReducer • AppStore͸AppReducerΛར༻ͯ࣍͠ͷAppStateΛܭࢉ͢Δ class AppStore( private val initialState: AppState = AppState() ) : StoreType { 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ͷड͚औΓ

Slide 43

Slide 43 text

AppStore → AppReducer • AppStore͸AppReducerΛར༻ͯ࣍͠ͷAppStateΛܭࢉ͢Δ class AppStore( private val initialState: AppState = AppState() ) : StoreType { 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 ঢ়ଶͷมߋ

Slide 44

Slide 44 text

AppStore → AppState 44

Slide 45

Slide 45 text

AppStore → AppState • AppStore͸ঢ়ଶ͕มߋ͞ΕΔ౓ʹAppStateΛ௨஌͢Δ • AppState͸Viewʹߪಡ͞ΕΔͷͰϝΠϯεϨουͰ௨஌͢Δ class AppStore( private val initialState: AppState = AppState() ) : StoreType { private val state = BehaviorRelay.createDefault(initialState) override fun observable(): Observable { return state.observeOn(AndroidSchedulers.mainThread()) } } 45

Slide 46

Slide 46 text

AppStore → AppState • AppStore͸ঢ়ଶ͕มߋ͞ΕΔ౓ʹAppStateΛ௨஌͢Δ • AppState͸Viewʹߪಡ͞ΕΔͷͰϝΠϯεϨουͰ௨஌͢Δ class AppStore( private val initialState: AppState = AppState() ) : StoreType { private val state = BehaviorRelay.createDefault(initialState) override fun observable(): Observable { return state.observeOn(AndroidSchedulers.mainThread()) } } 46 ঢ়ଶͷมߋ௨஌

Slide 47

Slide 47 text

AppState → View 47

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Ԡ༻ฤ 49

Slide 50

Slide 50 text

Ԡ༻ฤ • ࣮ࡍͷϓϩμΫτͰ͸ɺߟྀ͢΂͖͜ͱ͕ͨ͘͞Μ͢Δ • ඇಉظॲཧ • ը໘ભҠ • ঢ়ଶมߋͷෛՙ • ςετίʔυ 50

Slide 51

Slide 51 text

ඇಉظॲཧ • Redux͸ঢ়ଶ؅ཧʹಛԽ͓ͯ͠ΓɺඇಉظॲཧΛࡹ͘࢓૊ΈΛ࣋ͨͳ͍ • Reduxͷελϯε͸ReducerҎલͰඇಉظॲཧΛࡹ͘͜ͱ • ͦͷݱΕͱͯ͠ɺReducerҎ߱͸ඞͣಉظॲཧͰهड़͢Δඞཁ͕͋Δ • ࣮ࡍ͸ඇಉظॲཧ͸ඞਢͰ͋ΓɺMiddlewareΛ༻͍࣮ͨ૷͕ओྲྀ • redux-thunk • redux-saga • etc… 51

Slide 52

Slide 52 text

Middleware • StoreͰͷঢ়ଶมߋલޙʹॲཧΛڬΉͨΊͷػߏ • Middlewareͷ࣮૷ྫ • ϩΪϯάॲཧ • ඇಉظॲཧ • etc… • ෳ਺ͷMiddlewareΛઃఆ͢Δ͜ͱ͕Մೳ 52

Slide 53

Slide 53 text

53

Slide 54

Slide 54 text

LoggerMiddleware • ϩάग़ྗ͸Middlewareͷ࠷΋୯७ͳར༻ྫ • Dispatch͞ΕͨActionΛஞҰϩάʹग़ྗ͢Δ • ඞཁʹԠͯ͡StateΛग़ྗ͢Δ͜ͱ΋Մೳ class LoggerMiddleware : MiddlewareType { override fun before(state: StateType, action: ActionType): Single { Log.d("ReduxKit", "Before dispatching: ${action::class.java.simpleName}") return Single.just(action) } override fun after(state: StateType, action: ActionType): Single { Log.d("ReduxKit", "After dispatching: ${action::class.java.simpleName}") return Single.just(action) } } 54

Slide 55

Slide 55 text

ThunkMiddleware • ඇಉظॲཧΛߦ͏ࡍͷ࠷΋ҰൠతͳΞϓϩʔν 55

Slide 56

Slide 56 text

ThunkMiddleware interface AsyncActionType : ActionType { fun execute(dispatcher: Dispatcher): Single } class ThunkMiddleware( private val dispatcher: Dispatcher ) : MiddlewareType { override fun before(state: StateType, action: ActionType): Single { return if (action is AsyncActionType) { action.execute(dispatcher) } else { Single.just(action) } } override fun after(state: StateType, action: ActionType): Single { return Single.just(action) } } 56

Slide 57

Slide 57 text

ThunkMiddleware interface AsyncActionType : ActionType { fun execute(dispatcher: Dispatcher): Single } class ThunkMiddleware( private val dispatcher: Dispatcher ) : MiddlewareType { override fun before(state: StateType, action: ActionType): Single { return if (action is AsyncActionType) { action.execute(dispatcher) } else { Single.just(action) } } override fun after(state: StateType, action: ActionType): Single { return Single.just(action) } } 57 ඇಉظॲཧ༻ͷInterface

Slide 58

Slide 58 text

ThunkMiddleware interface AsyncActionType : ActionType { fun execute(dispatcher: Dispatcher): Single } class ThunkMiddleware( private val dispatcher: Dispatcher ) : MiddlewareType { override fun before(state: StateType, action: ActionType): Single { return if (action is AsyncActionType) { action.execute(dispatcher) } else { Single.just(action) } } override fun after(state: StateType, action: ActionType): Single { return Single.just(action) } } 58 ඇಉظॲཧͷ৔߹͚࣮ͩߦ͢Δ

Slide 59

Slide 59 text

ThunkMiddleware interface AsyncActionType : ActionType { fun execute(dispatcher: Dispatcher): Single } class ThunkMiddleware( private val dispatcher: Dispatcher ) : MiddlewareType { override fun before(state: StateType, action: ActionType): Single { return if (action is AsyncActionType) { action.execute(dispatcher) } else { Single.just(action) } } override fun after(state: StateType, action: ActionType): Single { return Single.just(action) } } 59 Dispatcherͱ͸ʁ

Slide 60

Slide 60 text

Dispatcher • ඇಉظʹॲཧΛߦ͏ؒʹผͷActionΛൃߦ͢ΔͨΊͷ࢓૊Έ • ྫ͑͹ɺαʔόʔ͔ΒσʔλΛऔಘ͍ͯ͠ΔؒʹϩʔσΟϯά Λදࣔ͢Δ 60

Slide 61

Slide 61 text

61

Slide 62

Slide 62 text

ը໘ભҠ • Redux͸ঢ়ଶ؅ཧʹಛԽ͍ͯ͠Δ • ͦ΋ͦ΋ը໘ભҠ͸ঢ়ଶͳͷ͔ʁ • BooleanͰແཧ΍Γঢ়ଶͱͯ͠දݱͰ͖ͳ͘΋ͳ͍ • Ұ࣌తͳ΋ͷͳͷͰঢ়ଶͱͯ͠͸දݱ͠ʹ͍͘ • ઈରతͳਖ਼ղ͸ͳ͍͕ɺReduxͱ͸ผʹ࣮૷͢Δͷ͕ແ೉ 62

Slide 63

Slide 63 text

ঢ়ଶมߋͷෛՙ • ReduxͰ͸ΞϓϦશମͷঢ়ଶΛ1ͭͷStateͰදݱ͢Δ • Stateͷઃܭʹ໌֬ͳࢦ਑͸ଘࡏ͠ͳ͍ • ୯७ͳΞϓϦͷ৔߹ɺը໘୯ҐͰStateΛఆٛ͢Ε͹ྑ͍ • ෳࡶͳΞϓϦͷ৔߹ɺը໘୯ҐͰStateΛఆٛ͢Δͱσʔλͷॏ ෳ͕໰୊ͱͳΔ 63

Slide 64

Slide 64 text

σʔλॏෳ • TodoΞϓϦʹػೳΛ௥Ճ͢Δ৔߹Λߟ͑Δ • Todoͷىථऀͱ͍͏֓೦Λ௥Ճ͢Δ • શͯͷTodo͕දࣔ͞ΕΔը໘Λ࣮૷͢Δ • ࣗ෼ͷTodo͚͕ͩදࣔ͞ΕΔը໘Λ࣮૷͢Δ 64

Slide 65

Slide 65 text

65

Slide 66

Slide 66 text

σʔλͷਖ਼نԽ • RDBͷΑ͏ʹσʔλΛਖ਼نԽͯ͠ΈΔ 66

Slide 67

Slide 67 text

67

Slide 68

Slide 68 text

68

Slide 69

Slide 69 text

αϯϓϧ • GitHubΫϥΠΞϯτ • ݕࡧը໘ • ελʔҰཡը໘ • ٕज़ཁૉ • ඇಉظॲཧ • σʔλͷਖ਼نԽ 69 ݕࡧը໘ ελʔҰཡը໘

Slide 70

Slide 70 text

શମ૾ 70

Slide 71

Slide 71 text

AppState 71

Slide 72

Slide 72 text

AppState • ΞϓϦશମͷঢ়ଶΛදݱ͢ΔΫϥε data class AppState( val domain: DomainState = DomainState(), val search: SearchStoreState = SearchStoreState(), val star: StarStoreState = StarStoreState() ) : StateType { fun toSearchViewState(): SearchViewState fun toStarViewState(): StarViewState } 72

Slide 73

Slide 73 text

AppState • ΞϓϦશମͷঢ়ଶΛදݱ͢ΔΫϥε data class AppState( val domain: DomainState = DomainState(), val search: SearchStoreState = SearchStoreState(), val star: StarStoreState = StarStoreState() ) : StateType { fun toSearchViewState(): SearchViewState fun toStarViewState(): StarViewState } 73 υϝΠϯΤϯςΟςΟΛදݱ͢ΔΫϥε

Slide 74

Slide 74 text

AppState • ΞϓϦશମͷঢ়ଶΛදݱ͢ΔΫϥε data class AppState( val domain: DomainState = DomainState(), val search: SearchStoreState = SearchStoreState(), val star: StarStoreState = StarStoreState() ) : StateType { fun toSearchViewState(): SearchViewState fun toStarViewState(): StarViewState } 74 ݕࡧը໘ͷঢ়ଶΛදݱ͢ΔΫϥε

Slide 75

Slide 75 text

AppState • ΞϓϦશମͷঢ়ଶΛදݱ͢ΔΫϥε data class AppState( val domain: DomainState = DomainState(), val search: SearchStoreState = SearchStoreState(), val star: StarStoreState = StarStoreState() ) : StateType { fun toSearchViewState(): SearchViewState fun toStarViewState(): StarViewState } 75 ελʔҰཡը໘ͷঢ়ଶΛදݱ͢ΔΫϥε

Slide 76

Slide 76 text

76

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

AppAction 78

Slide 79

Slide 79 text

AppAction • ঢ়ଶมߋΛදݱ͢ΔΫϥε sealed class AppAction : ActionType { sealed class DomainAction : AppAction() sealed class SearchAction : AppAction() sealed class StarAction : AppAction() } 79

Slide 80

Slide 80 text

AppAction • ঢ়ଶมߋΛදݱ͢ΔΫϥε sealed class AppAction : ActionType { sealed class DomainAction : AppAction() sealed class SearchAction : AppAction() sealed class StarAction : AppAction() } 80 DomainStateͷঢ়ଶมߋΛදݱ͢ΔΫϥε

Slide 81

Slide 81 text

AppAction • ঢ়ଶมߋΛදݱ͢ΔΫϥε sealed class AppAction : ActionType { sealed class DomainAction : AppAction() sealed class SearchAction : AppAction() sealed class StarAction : AppAction() } 81 SearchStateͷঢ়ଶมߋΛදݱ͢ΔΫϥε

Slide 82

Slide 82 text

AppAction • ঢ়ଶมߋΛදݱ͢ΔΫϥε sealed class AppAction : ActionType { sealed class DomainAction : AppAction() sealed class SearchAction : AppAction() sealed class StarAction : AppAction() } 82 StarStateͷঢ়ଶมߋΛදݱ͢ΔΫϥε

Slide 83

Slide 83 text

AppReducer 83

Slide 84

Slide 84 text

AppReducer • AppStateͱAppAction͔ΒAppStateΛܭࢉ͢ΔΫϥε object AppReducer : ReducerType { 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

Slide 85

Slide 85 text

AppReducer • AppStateͱAppAction͔ΒAppStateΛܭࢉ͢ΔΫϥε object AppReducer : ReducerType { 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Λܭࢉ

Slide 86

Slide 86 text

AppReducer • AppStateͱAppAction͔ΒAppStateΛܭࢉ͢ΔΫϥε object AppReducer : ReducerType { 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Λܭࢉ

Slide 87

Slide 87 text

AppReducer • AppStateͱAppAction͔ΒAppStateΛܭࢉ͢ΔΫϥε object AppReducer : ReducerType { 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Λܭࢉ

Slide 88

Slide 88 text

AppStore 88

Slide 89

Slide 89 text

AppStore • AppStateͷอ࣋ɾมߋɾ௨஌ • MiddlewareΛར༻ͨ͠ϩάग़ྗɾඇಉظॲཧ 89

Slide 90

Slide 90 text

AppStore 90

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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Λى఺ʹॲཧΛ։࢝

Slide 93

Slide 93 text

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Λ࿈݁

Slide 94

Slide 94 text

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ͷঢ়ଶΛߋ৽

Slide 95

Slide 95 text

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Λ࿈݁

Slide 96

Slide 96 text

Ϣʔεέʔεຖͷ࣮૷ • ϦϙδτϦͷݕࡧॲཧ • ݕࡧը໘ʹೖྗ͞ΕͨΫΤϦΛ΋ͱʹݕࡧΛ࣮ߦ • ݕࡧ݁ՌΛҰཡͰදࣔ • ϦϙδτϦͷελʔॲཧ • ϦϙδτϦͷݕࡧ݁Ռ͔ΒελʔॲཧΛ࣮ߦ • ελʔॲཧ׬ྃޙʹɺݕࡧը໘ͱελʔҰཡը໘ʹ൓ө 96

Slide 97

Slide 97 text

ϦϙδτϦͷݕࡧॲཧ • ॲཧͷྲྀΕ • ݕࡧॲཧΛඇಉظͰ࣮ߦ͢Δ • ݕࡧॲཧͷ։࢝࣌ʹϩʔσΟϯάΛදࣔ͢Δ • ݕࡧॲཧͷऴྃ࣌ʹϩʔσΟϯάΛඇදࣔʹ͢Δ • ݕࡧ݁ՌΛݕࡧը໘ʹ൓ө͢Δ 97

Slide 98

Slide 98 text

class SearchActionCreator @Inject constructor( private val repository: GitHubRepository ) { fun fetchSearchRepositories(query: String): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single { 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

Slide 99

Slide 99 text

class SearchActionCreator @Inject constructor( private val repository: GitHubRepository ) { fun fetchSearchRepositories(query: String): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single { 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 ݕࡧॲཧΛ࣮ߦ

Slide 100

Slide 100 text

class SearchActionCreator @Inject constructor( private val repository: GitHubRepository ) { fun fetchSearchRepositories(query: String): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single { 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 ݕࡧॲཧͷ։࢝࣌ʹϩʔσΟϯάΛදࣔ

Slide 101

Slide 101 text

class SearchActionCreator @Inject constructor( private val repository: GitHubRepository ) { fun fetchSearchRepositories(query: String): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single { 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 ݕࡧॲཧͷऴྃ࣌ʹϩʔσΟϯάΛඇදࣔ

Slide 102

Slide 102 text

class SearchActionCreator @Inject constructor( private val repository: GitHubRepository ) { fun fetchSearchRepositories(query: String): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single { 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Λมߋ

Slide 103

Slide 103 text

class SearchActionCreator @Inject constructor( private val repository: GitHubRepository ) { fun fetchSearchRepositories(query: String): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single { 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 ݕࡧ݁ՌΛը໘ʹ൓ө

Slide 104

Slide 104 text

ϦϙδτϦͷελʔॲཧ • ॲཧͷྲྀΕ • ελʔॲཧΛඇಉظͰ࣮ߦ͢Δ • ελʔҰཡը໘ʹϦϙδτϦΛ௥Ճ͢Δ 104

Slide 105

Slide 105 text

class StarActionCreator @Inject constructor( private val repository: GitHubRepository ) { fun starRepo(repo: Repo): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single { 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

Slide 106

Slide 106 text

class StarActionCreator @Inject constructor( private val repository: GitHubRepository ) { fun starRepo(repo: Repo): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single { 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 ελʔॲཧΛ࣮ߦ

Slide 107

Slide 107 text

class StarActionCreator @Inject constructor( private val repository: GitHubRepository ) { fun starRepo(repo: Repo): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single { 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Λมߋ

Slide 108

Slide 108 text

class StarActionCreator @Inject constructor( private val repository: GitHubRepository ) { fun starRepo(repo: Repo): AsyncActionType { return object : AsyncActionType { override fun execute(dispatcher: Dispatcher): Single { 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 ελʔҰཡը໘ʹϦϙδτϦΛ௥Ճ

Slide 109

Slide 109 text

ςετίʔυ • AppState/AppAction • ೖΕ෺ΫϥεͳͷͰςετ͸ෆཁ • AppReducer • ঢ়ଶมߋͷܭࢉ͕ਖ਼͘͠ߦΘΕΔ͔ΛνΣοΫ͢Δ • ७ਮͳؔ਺ͱ࣮ͯ͠૷͍ͯ͠ΔͷͰඇৗʹςετָ͕ • AppStore • ঢ়ଶͷอ࣋ɾมߋɾ௨஌͕ਖ਼͘͠ߦΘΕΔ͔ΛνΣοΫ͢Δ • RxJava͕བྷΉ෦෼͸TestObserverΛར༻ͯ͠ςετ͢Δ 109

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

AppStore • ঢ়ଶͷอ࣋ɾมߋɾ௨஌͕ਖ਼͘͠ߦΘΕΔ͔ΛνΣοΫ͢Δ val state = AppState() val store = AppStore(state) val observer = TestObserver() 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

Slide 112

Slide 112 text

͓ΘΓʹ 112

Slide 113

Slide 113 text

ϝϦοτɾσϝϦοτ • ϝϦοτ • ୯ํ޲σʔλϑϩʔʹΑͬͯίʔυ͕௥͍΍͘͢ͳΔ • ঢ়ଶ؅ཧͷ࣮૷͕ΞϓϦશମͰ౷Ұ͞ΕΔ • AndroidͷϥΠϑαΠΫϧʹࠨӈ͞Εͳ͘ͳΔ • σϝϦοτ • Reduxͷࢥ૝Λཧղ͢Δ·Ͱ͸։ൃεϐʔυ͕μ΢ϯ • ίʔυྔ͕૿͑ɺσʔλਖ਼نԽ·ͰؚΊΔͱ࣮૷͕େม 113

Slide 114

Slide 114 text

ଞͷΞʔΩςΫνϟͱͷؔ܎ੑ 114 Presentation Model MVP ̋ ✕ MVVM ̋ ✕ Flux / Redux ̋ ✕ AAC ̋ ✕ Layered Architecture ✕ ̋

Slide 115

Slide 115 text

PairsͷΞʔΩςΫνϟ • Redux • MVVM • Layered Architecture 115

Slide 116

Slide 116 text

·ͱΊ • AndroidΞϓϦ։ൃͰ͸ঢ়ଶ؅ཧ͕೰·͍͠໰୊ • ը໘Λލ͍ͩঢ়ଶͷಉظ • ϥΠϑαΠΫϧͱͷ෇͖߹͍ํ • ReduxΛ࠾༻͢Δ͜ͱͰঢ়ଶ؅ཧʹ·ͭΘΔ໰୊͕ղফՄೳ • ঢ়ଶΛதԝूݖతʹ؅ཧ • ϥΠϑαΠΫϧͱঢ়ଶ؅ཧΛ෼཭ 116

Slide 117

Slide 117 text

·ͱΊ • جૅฤ • Stateɿঢ়ଶΛදݱ͢Δ΋ͷ • Actionɿঢ়ଶͷมߋ಺༰Λදݱ͢Δ΋ͷ • Reducerɿ࣍ͷঢ়ଶΛܭࢉ͢Δ΋ͷ • Storeɿঢ়ଶอ࣋ͱมߋΛ୲౰͢Δ΋ͷ • Ԡ༻ฤ • ඇಉظॲཧɿMiddlewareͰͷ࣮૷͕ओྲྀ • ը໘ભҠɿReduxͱ͸ผͰ࣮૷͢Δ • σʔλͷਖ਼نԽɿσʔλͷมߋෛՙΛԼ͛Δ • ςετίʔυɿReducer͸؆୯ɺStore͸TestObserverΛ࢖͏ 117

Slide 118

Slide 118 text

118 Credit: NASA Earth Observatory/NOAA NGDC Thank you :)