droidkaigi-2019

836437265cbba12ee1b24bbb359a6c15?s=47 Yuya Kaido
February 07, 2019

 droidkaigi-2019

836437265cbba12ee1b24bbb359a6c15?s=128

Yuya Kaido

February 07, 2019
Tweet

Transcript

  1. Redux for Android yuyakaido DroidKaigi 2019

  2. ࣗݾ঺հ • ւ౻༏໻ʢ͔͍Ͳ͏Ώ͏΍ʣ • גࣜձࣾΤ΢ϨΧ • AndroidΤϯδχΞ • DroidKaigi •

    2016 - ςετ͓͡͞Μ • 2017 - RxJavaͷΤϥʔϋϯυϦϯά • 2018 - ϚϧνϩάΠϯͷ࣮૷ํ๏ • 2019 - Redux for Android 2 yuyakaido
  3. Pairs Japan / Global • ೔ຊ࠷େͷϚονϯάαʔϏε • ୆࿷ɾؖࠃͰ΋αʔϏεల։ • ձһ਺ɿ1000ສਓ

    • Ϛονϯά਺ɿ5600ສ૊ • Χοϓϧ਺ɿ10ສ૊ 3
  4. ໨࣍ • ͸͡Ίʹ • AndroidΞϓϦ։ൃʹ͓͚Δ೰·͍͠໰୊ɺଞͷϓϥοτϑΥʔϜͰͷղܾࡦ • جૅฤ • Reduxͷશମ૾ͱߏ੒ཁૉ •

    αϯϓϧɿTODOΞϓϦ • Ԡ༻ฤ • ඇಉظॲཧɺը໘ભҠɺঢ়ଶมߋͷෛՙɺςετίʔυ • αϯϓϧɿGitHubΫϥΠΞϯτ • ͓ΘΓʹ • ReduxͷϝϦοτɾσϝϦοτ • ଞͷΞʔΩςΫνϟͱͷؔ܎ੑ • ·ͱΊ 4
  5. ͸͡Ίʹ 5

  6. Android։ൃʹ͓͚Δ೰·͍͠໰୊ • ୺຤΍OSґଘͷڍಈ • iOS༏੎ͳ೔ຊࢢ৔ • ෳࡶͳঢ়ଶ؅ཧ • ը໘Λލ͍ͩঢ়ଶͷಉظ •

    ϥΠϑαΠΫϧͱͷ෇͖߹͍ํ 6
  7. ը໘Λލ͍ͩঢ়ଶͷಉظ • ෳ਺ͷը໘ͰಉҰͷ৘ใ͕දࣔ͞Ε͍ͯΔ৔߹ɺ͋Δը໘Ͱߦ ΘΕͨঢ়ଶมߋΛଞͷը໘ʹ΋ಉظ͢Δඞཁ͕͋Δ • ඪ४APIΛ༻͍ͯঢ়ଶΛಉظ͢Δ৔߹͸ɺը໘ؒͰ૒ํ޲ʹσʔ λͷ΍ΓऔΓΛߦ͏ • Կ͕໰୊ʁ •

    ը໘͕૒ํ޲ʹґଘͯ͠Մಡੑ͕ѱ͍ 7
  8. ϥΠϑαΠΫϧͱͷ෇͖߹͍ํ • ਺ଟ͘ͷϥΠϑαΠΫϧ͕ଘࡏ͠ɺϥΠϑαΠΫϧʹ߹Θͤͯঢ় ଶΛ؅ཧ͢Δඞཁ͕͋Δ • ը໘ճస΍ϝϞϦෆ଍ʹΑΔը໘ͷ࠶ੜ੒ͱ͍ͬͨΠϨΪϡϥʔ έʔε΋ߟྀ͢Δඞཁ͕͋Δ • Կ͕໰୊ʁ •

    ϥΠϑαΠΫϧͱঢ়ଶ؅ཧͱ͍͏ຊ࣭తʹ͸ແؔ܎ͷ΋ͷ͕ ಉډ͍ͯ͠Δ 8
  9. ෳࡶͳঢ়ଶ؅ཧͷ໰୊఺ • ঢ়ଶΛಉظ͢ΔͨΊʹը໘͕૒ํ޲ʹґଘ͍ͯ͠Δ • ϥΠϑαΠΫϧͱঢ়ଶ؅ཧ͕ಉډ͍ͯ͠Δ 9

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

    ͦͷޙ΋ଟ͘ͷ࣮૷ྫ͕ొ৔͠ɺ࠷΋ϝδϟʔͳ΋ͷ͕Redux 10
  11. Redux • ୯ํ޲σʔλϑϩʔΛϕʔεͱͨ͠ΞʔΩςΫνϟ • ঢ়ଶΛதԝͰҰׅ؅ཧ͠ɺը໘͸ͦΕΛඳը͢Δ͚ͩ • ReduxͰঢ়ଶ؅ཧͷ໰୊ΛղফͰ͖ΔͷͰ͸ͳ͍͔ʁ • ঢ়ଶΛதԝͰҰׅ؅ཧ͢Δ͜ͱͰը໘͕૒ํ޲ʹґଘ͠ͳ͍ •

    ঢ়ଶ؅ཧͱϥΠϑαΠΫϧΛ෼཭͢Δ͜ͱ͕Ͱ͖Δ 11
  12. جૅฤ 12

  13. Reduxͷߏ੒ཁૉ 13

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

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

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

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

  18. αϯϓϧ • TodoΞϓϦ • TodoҰཡ • Todo௥Ճ • ٕज़ཁૉ •

    Reduxͷશମ૾ • ֤ཁૉͷ࣮૷ • ঢ়ଶมߋͷखॱ • ϦϙδτϦ • https://github.com/yuyakaido/ReduxKit 18
  19. શମ૾ 19

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

  21. AppState 21

  22. AppState • ΞϓϦશମͷঢ়ଶΛදݱ͢ΔΫϥε • Data Classͱͯ͠ఆٛ͢Δͱঢ়ଶมߋָ͕ data class AppState( val

    todos: List<Todo> = emptyList() ) : StateType data class Todo( val title: String ) 22
  23. AppAction 23

  24. AppAction • ঢ়ଶมߋΛදݱ͢ΔΫϥε • Sealed ClassͰ࣮૷͢Δ͜ͱͰReducerΛγϯϓϧʹͰ͖Δ sealed class AppAction :

    ActionType { data class RefreshTodos(val todos: List<Todo>) : AppAction() data class AddTodo(val todo: Todo) : AppAction() } 24
  25. AppReducer 25

  26. 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
  27. AppStore 27

  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()) } } 28
  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()) } } 29 ॳظ஋ͷड͚औΓ
  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()) } } 30 ঢ়ଶͷอ࣋
  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()) } } 31 ঢ়ଶͷมߋ
  32. 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 ঢ়ଶͷ௨஌
  33. View 33

  34. 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
  35. Ϣʔεέʔε • TodoҰཡ • Todo௥Ճ 35

  36. View → AppAction 36

  37. View → AppAction • FloatingActionButtonͷԡԼ࣌ʹAppActionΛੜ੒͢Δ • ࠓճ͸Todoͷ௥ՃΛߦ͏ͷͰɺAppAction.AddTodoΛੜ੒͢Δ binding.floatingActionButton .setOnClickListener {

    val todo = Todo(" NewTask!") val action = AppAction.AddTodo(todo) } 37
  38. AppAction → AppStore 38

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

    val action = AppAction.AddTodo(todo) getAppStore().dispatch(action) 39
  40. AppStore → AppReducer 40

  41. AppStore → AppReducer • AppStore͸AppReducerΛར༻ͯ࣍͠ͷ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
  42. AppStore → AppReducer • AppStore͸AppReducerΛར༻ͯ࣍͠ͷ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ͷड͚औΓ
  43. AppStore → AppReducer • AppStore͸AppReducerΛར༻ͯ࣍͠ͷ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 ঢ়ଶͷมߋ
  44. AppStore → AppState 44

  45. AppStore → AppState • AppStore͸ঢ়ଶ͕มߋ͞ΕΔ౓ʹAppStateΛ௨஌͢Δ • AppState͸Viewʹߪಡ͞ΕΔͷͰϝΠϯεϨουͰ௨஌͢Δ 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
  46. AppStore → AppState • AppStore͸ঢ়ଶ͕มߋ͞ΕΔ౓ʹAppStateΛ௨஌͢Δ • AppState͸Viewʹߪಡ͞ΕΔͷͰϝΠϯεϨουͰ௨஌͢Δ 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 ঢ়ଶͷมߋ௨஌
  47. AppState → View 47

  48. 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. Ԡ༻ฤ 49

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

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

    redux-thunk • redux-saga • etc… 51
  52. Middleware • StoreͰͷঢ়ଶมߋલޙʹॲཧΛڬΉͨΊͷػߏ • Middlewareͷ࣮૷ྫ • ϩΪϯάॲཧ • ඇಉظॲཧ •

    etc… • ෳ਺ͷMiddlewareΛઃఆ͢Δ͜ͱ͕Մೳ 52
  53. 53

  54. 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
  55. ThunkMiddleware • ඇಉظॲཧΛߦ͏ࡍͷ࠷΋ҰൠతͳΞϓϩʔν 55

  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) } } 56
  57. 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
  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) } } 58 ඇಉظॲཧͷ৔߹͚࣮ͩߦ͢Δ
  59. 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ͱ͸ʁ
  60. Dispatcher • ඇಉظʹॲཧΛߦ͏ؒʹผͷActionΛൃߦ͢ΔͨΊͷ࢓૊Έ • ྫ͑͹ɺαʔόʔ͔ΒσʔλΛऔಘ͍ͯ͠ΔؒʹϩʔσΟϯά Λදࣔ͢Δ 60

  61. 61

  62. ը໘ભҠ • Redux͸ঢ়ଶ؅ཧʹಛԽ͍ͯ͠Δ • ͦ΋ͦ΋ը໘ભҠ͸ঢ়ଶͳͷ͔ʁ • BooleanͰແཧ΍Γঢ়ଶͱͯ͠දݱͰ͖ͳ͘΋ͳ͍ • Ұ࣌తͳ΋ͷͳͷͰঢ়ଶͱͯ͠͸දݱ͠ʹ͍͘ •

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

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

  65. 65

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

  67. 67

  68. 68

  69. αϯϓϧ • GitHubΫϥΠΞϯτ • ݕࡧը໘ • ελʔҰཡը໘ • ٕज़ཁૉ •

    ඇಉظॲཧ • σʔλͷਖ਼نԽ 69 ݕࡧը໘ ελʔҰཡը໘
  70. શମ૾ 70

  71. AppState 71

  72. AppState • ΞϓϦશମͷঢ়ଶΛදݱ͢ΔΫϥε data class AppState( val domain: DomainState =

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

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

    DomainState(), val search: SearchStoreState = SearchStoreState(), val star: StarStoreState = StarStoreState() ) : StateType { fun toSearchViewState(): SearchViewState fun toStarViewState(): StarViewState } 74 ݕࡧը໘ͷঢ়ଶΛදݱ͢ΔΫϥε
  75. 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. 76

  77. 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
  78. AppAction 78

  79. AppAction • ঢ়ଶมߋΛදݱ͢ΔΫϥε sealed class AppAction : ActionType { sealed

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

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

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

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

  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)) } } } } 84
  85. 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Λܭࢉ
  86. 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Λܭࢉ
  87. 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Λܭࢉ
  88. AppStore 88

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

  90. AppStore 90

  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() } } 91
  92. 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Λى఺ʹॲཧΛ։࢝
  93. 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Λ࿈݁
  94. 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ͷঢ়ଶΛߋ৽
  95. 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. Ϣʔεέʔεຖͷ࣮૷ • ϦϙδτϦͷݕࡧॲཧ • ݕࡧը໘ʹೖྗ͞ΕͨΫΤϦΛ΋ͱʹݕࡧΛ࣮ߦ • ݕࡧ݁ՌΛҰཡͰදࣔ • ϦϙδτϦͷελʔॲཧ •

    ϦϙδτϦͷݕࡧ݁Ռ͔ΒελʔॲཧΛ࣮ߦ • ελʔॲཧ׬ྃޙʹɺݕࡧը໘ͱελʔҰཡը໘ʹ൓ө 96
  97. ϦϙδτϦͷݕࡧॲཧ • ॲཧͷྲྀΕ • ݕࡧॲཧΛඇಉظͰ࣮ߦ͢Δ • ݕࡧॲཧͷ։࢝࣌ʹϩʔσΟϯάΛදࣔ͢Δ • ݕࡧॲཧͷऴྃ࣌ʹϩʔσΟϯάΛඇදࣔʹ͢Δ •

    ݕࡧ݁ՌΛݕࡧը໘ʹ൓ө͢Δ 97
  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) } } } } } 98
  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) } } } } } 99 ݕࡧॲཧΛ࣮ߦ
  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) } } } } } 100 ݕࡧॲཧͷ։࢝࣌ʹϩʔσΟϯάΛදࣔ
  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) } } } } } 101 ݕࡧॲཧͷऴྃ࣌ʹϩʔσΟϯάΛඇදࣔ
  102. 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Λมߋ
  103. 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. ϦϙδτϦͷελʔॲཧ • ॲཧͷྲྀΕ • ελʔॲཧΛඇಉظͰ࣮ߦ͢Δ • ελʔҰཡը໘ʹϦϙδτϦΛ௥Ճ͢Δ 104

  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) } } } } } 105
  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) } } } } } 106 ελʔॲཧΛ࣮ߦ
  107. 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Λมߋ
  108. 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 ελʔҰཡը໘ʹϦϙδτϦΛ௥Ճ
  109. ςετίʔυ • AppState/AppAction • ೖΕ෺ΫϥεͳͷͰςετ͸ෆཁ • AppReducer • ঢ়ଶมߋͷܭࢉ͕ਖ਼͘͠ߦΘΕΔ͔ΛνΣοΫ͢Δ •

    ७ਮͳؔ਺ͱ࣮ͯ͠૷͍ͯ͠ΔͷͰඇৗʹςετָ͕ • AppStore • ঢ়ଶͷอ࣋ɾมߋɾ௨஌͕ਖ਼͘͠ߦΘΕΔ͔ΛνΣοΫ͢Δ • RxJava͕བྷΉ෦෼͸TestObserverΛར༻ͯ͠ςετ͢Δ 109
  110. 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
  111. 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. ͓ΘΓʹ 112

  113. ϝϦοτɾσϝϦοτ • ϝϦοτ • ୯ํ޲σʔλϑϩʔʹΑͬͯίʔυ͕௥͍΍͘͢ͳΔ • ঢ়ଶ؅ཧͷ࣮૷͕ΞϓϦશମͰ౷Ұ͞ΕΔ • AndroidͷϥΠϑαΠΫϧʹࠨӈ͞Εͳ͘ͳΔ •

    σϝϦοτ • Reduxͷࢥ૝Λཧղ͢Δ·Ͱ͸։ൃεϐʔυ͕μ΢ϯ • ίʔυྔ͕૿͑ɺσʔλਖ਼نԽ·ͰؚΊΔͱ࣮૷͕େม 113
  114. ଞͷΞʔΩςΫνϟͱͷؔ܎ੑ 114 Presentation Model MVP ̋ ✕ MVVM ̋ ✕

    Flux / Redux ̋ ✕ AAC ̋ ✕ Layered Architecture ✕ ̋
  115. PairsͷΞʔΩςΫνϟ • Redux • MVVM • Layered Architecture 115

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

    ঢ়ଶΛதԝूݖతʹ؅ཧ • ϥΠϑαΠΫϧͱঢ়ଶ؅ཧΛ෼཭ 116
  117. ·ͱΊ • جૅฤ • Stateɿঢ়ଶΛදݱ͢Δ΋ͷ • Actionɿঢ়ଶͷมߋ಺༰Λදݱ͢Δ΋ͷ • Reducerɿ࣍ͷঢ়ଶΛܭࢉ͢Δ΋ͷ •

    Storeɿঢ়ଶอ࣋ͱมߋΛ୲౰͢Δ΋ͷ • Ԡ༻ฤ • ඇಉظॲཧɿMiddlewareͰͷ࣮૷͕ओྲྀ • ը໘ભҠɿReduxͱ͸ผͰ࣮૷͢Δ • σʔλͷਖ਼نԽɿσʔλͷมߋෛՙΛԼ͛Δ • ςετίʔυɿReducer͸؆୯ɺStore͸TestObserverΛ࢖͏ 117
  118. 118 Credit: NASA Earth Observatory/NOAA NGDC Thank you :)