Save 37% off PRO during our Black Friday Sale! »

Flux de Relax :)

Flux de Relax :)

Flux implementation for Android

9f4db21673dc0e7cbc313df8310309fb?s=128

Masaki Ogata

August 07, 2016
Tweet

Transcript

  1. Flux de Relax :) ANDROID ALLSTARS #2 @dots. Masaki Ogata

  2. About me Masaki Ogata ogaclejapan CyberAgent, Inc. / AbemaTV, Inc.

    @ogaclejapan
  3. Flux de Relax :)

  4. What is Flux?

  5. Facebook Flux Architecture IUUQTGBDFCPPLHJUIVCJPqVYEPDTPWFSWJFXIUNM “Data in a Flux application flows

    in a single direction”
  6. Facebook Flux Architecture IUUQTHJUIVCDPNGBDFCPPLqVY

  7. Facebook Flux Architecture IUUQTHJUIVCDPNGBDFCPPLqVY Observerύλʔϯ

  8. Why Flux?

  9. Why Flux? ΞϓϦέʔγϣϯͷ։ൃͰ Viewͷঢ়ଶ؅ཧ͕Ұ൪೉͍͠ :(

  10. Why Flux? AbemaTVͰඞཁʹͳΔViewͷঢ়ଶ؅ཧ: Cast ՝ۚ CM ࢹௌ༧໿ ΦϯσϚϯυ ը࣭ ϑΟϥʔ

    etc…
  11. Sample code https://github.com/ogaclejapan/ FluxArchitectureSample *OQVU'SBHNFOU 3FTVMU'SBHNFOU

  12. Flux Architecture Sample

  13. Flux: Dispatcher

  14. Flux: Dispatcher N:1 1:N

  15. Flux: Dispatcher /* Store */ Dispatcher.register(function(payload) { switch(payload.actionType) { case

    'foo': ... = payload.data // Do something } } /* Action */ Dispatcher.dispatch({ actionType: 'foo', payload: 'value' }); ຊՈFluxͷ࣮૷Λࢀߟʹͯ͠ΈΔ
  16. Flux: Dispatcher JavaͰ࣮૷Λॻ͖௚ͯ͠ΈΔͱ… public interface Action { String getType(); }

    public class FooAction implements Action {...} /* Action */ Dispatcher.dispatch(new FooAction(data)); /* Store */ Dispatcher.register(new Callback() { public void on(Action action) { switch (action.getType()) { case "foo": ... = ((FooAction) action).data; break; } } }
  17. Flux: Dispatcher JavaͰ࣮૷Λॻ͖௚ͯ͠ΈΔͱ… public interface Action { String getType(); }

    public class FooAction implements Action {...} /* Action */ Dispatcher.dispatch(new FooAction(data)); /* Store */ Dispatcher.register(new Callback() { public void on(Action action) { switch (action.getType()) { case "foo": ... = ((FooAction) action).data; break; } } } Javaͩͱܕม׵͕ඞཁ :(
  18. Flux: Dispatcher IUUQTHJUIVCDPNHSFFOSPCPU&WFOU#VT // Define events: public class MessageEvent {

    /* Additional fields if needed */ } // Prepare subscribers: Register your subscriber eventBus.register(this); // Declare your subscribing method: @Subscribe public void onEvent(AnyEventType event) {/* Do something */}; // Post events: eventBus.post(event); …EventBusͰΑ͘Ͷ͐ʁ
  19. Flux: Dispatcher public class Dispatcher { private final EventBus bus;

    public Dispatcher() { bus = EventBus.builder() ... .build(); } public void dispatch(Object payload) { bus.post(payload); } public void register(Object observer) { bus.register(observer); } public void unregister(Object observer) { bus.unregister(observer); } }
  20. Flux: Action

  21. Flux: Action Actionͷσʔλϑϩʔ ٩(•౪• ٩)

  22. Flux: Action View͔ΒͷೖྗʹΑΓσʔλ͕ྲྀΕͯ͘Δ

  23. Flux: Action σʔλιʔεʹඞཁͱͳΔσʔλΛऔΓʹߦ͘

  24. Flux: Action σʔλ͕ू·ͬͨΒDispatcher΁σʔλΛྲྀ͢

  25. Flux: Action Web, DB, DevicesΛ֎෦αʔϏεͱͯ͠ଊ͑Δ

  26. Flux: Action @Inject GitHubApi gitHubApi; private final Dispatcher dispatcher; @Inject

    public UserSearchAction(Dispatcher dispatcher) { this.dispatcher = dispatcher; } public void findFollower(String userId) { ... } public void findFollower(String userId, int nextPage) { gitHubApi.followers(userId, nextPage) .doOnSubscribe(() -> dispatchState(LoadingState.LOADING)) .subscribe(users -> { dispatcher.dispatch(new SearchResultListChangedEvent( userId, users, users.nextPage())); dispatchState(users.hasMore() ? LoadingState.LOADABLE : LoadingState.FINISHED); }, ...); }
  27. Flux: Action Tips: ֎෦I/Fͷ໭Γ஋ΛRxͰ౷Ұ͓ͯ͘͠ @Inject GitHubApi gitHubApi; public void findFollower(String

    userId, int nextPage) { Observable.zip( gitHubApi.followers(userId, nextPage), gitHubApi.user(userId), this::doSomething) .subscribe(...) }
  28. Flux: Action Tips: Ωϟϯηϧॲཧ͕ඞཁͳͱ͖ // e.g. ॲཧΛݺͼग़͠ଆͰΩϟϯηϧ͢Δ public Subscription findFollower(String

    userId, int nextPage) { return gitHubApi.followers(userId, nextPage) .subscribe(...); } // e.g. ॲཧ͕࣮ߦதͳΒΩϟϯηϧ͢Δ private Subscription subs = Subscriptions.empty(); public void findFollower(String userId, int nextPage) { if (!subs.isUnsubscribed()) subs.unsubscribe(); subs = gitHubApi.followers(userId, nextPage) .subscribe(...); }
  29. Flux: Store

  30. Flux: Store Storeͷσʔλϑϩʔ ₍₍ (ง ˙ω˙)ว ⁾⁾

  31. Flux: Store σʔλΛड͚औΔͨΊʹCallbackΛొ࿥͢Δ

  32. Flux: Store Action͔ΒσʔλΛྲྀ͢ͱCallback΁ྲྀΕͯ͘Δ

  33. Flux: Store σʔλΛड͚औͬͨΒStore಺σʔλΛߋ৽͢Δ

  34. Flux: Store σʔλΛߋ৽ͨ͠ΒView΁มߋΛ௨஌͢Δ

  35. Flux: Store @ActivityScope public class UserSearchStore { @Inject public UserSearchStore(Dispatcher

    dispatcher, ActivityLifecycleHook hook) { hook.addOnCreate(() -> dispatcher.register(this)); hook.addOnDestroy(() -> dispatcher.unregister(this)); } Dispatcher΁CallbackΛొ࿥͢Δ
  36. Flux: Store @Singleton @ActivityScope public class UserSearchStore { @Inject public

    UserSearchStore(Dispatcher dispatcher, ActivityLifecycleHook hook) { dispatcher.register(this); hook.addOnCreate(() -> dispatcher.register(this)); hook.addOnDestroy(() -> dispatcher.unregister(this)); } Dispatcher΁CallbackΛొ࿥͢Δ
  37. Flux: Store CallbackॲཧͰࣗ਎ͷঢ়ଶΛߋ৽͢Δ private final ObservableField<LoadingState> state = new ObservableField<>(LoadingState.LOADABLE);

    @Subscribe(threadMode = ThreadMode.MAIN) public void on(SearchLoadingStateChangedEvent event) { state.set(event.state); }
  38. Flux: Store ঢ়ଶมߋΛ௨஌͢ΔͨΊͷϝιουΛެ։͢Δ private final ObservableField<LoadingState> state = new ObservableField<>(LoadingState.LOADABLE);

    public Disposer addOnLoadingStateChanged( OnFieldChangedCallback<LoadingState> cb) { state.addOnPropertyChangedCallback(cb); return Disposers.from(() -> removeOnLoadingStateChanged(cb)); } public void removeOnLoadingStateChanged( OnFieldChangedCallback<LoadingState> cb) { state.removeOnPropertyChangedCallback(cb); }
  39. Flux: Store Tips: ObservableXXͷ୅ΘΓʹRxΛ࢖͏ private final BehaviorSubject<LoadingState> state = BehaviorSubject.create(LoadingState.LOADABLE);

    public Observable<LoadingState> state() { return state.asObservable(); } @Subscribe(threadMode = ThreadMode.MAIN) public void on(SearchLoadingStateChangedEvent event) { state.onNext(event.state); }
  40. Flux: View

  41. Flux: View Viewͷσʔλϑϩʔ ٩(๑´3ʆ๑)۶

  42. Flux: View σʔλΛड͚औΔͨΊStore΁CallbackΛొ࿥͢Δ

  43. Flux: View σʔλ͕ߋ৽͞ΕͨΒCallback͕ݺ͹ΕΔ

  44. Flux: View σʔλΛड͚औͬͨΒը໘Λߋ৽͢Δ

  45. Flux: View ৽ͨͳೖྗ͕ൃੜͨ͠ΒAction΁σʔλΛྲྀ͢

  46. Flux: View Storeͷঢ়ଶʹԠͯ͡ViewΛߋ৽͢Δ @Inject UserSearchStore userSearchStore; private final OnListChangedCallback<User> resultListChanged

    = new OnListChangedCallback<User>() { @Override public void onChanged(ObservableList<User> sender) { binding.setItemCount(sender.size()); } }; public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { ... userSearchStore.addOnListChanged(resultListChanged).addTo(this); }
  47. Flux: View Storeͷঢ়ଶʹԠͯ͡AdapterΛߋ৽͢Δ @Inject public UserSearchListAdapter(UserSearchStore store, ActivityLifecycleHook hook) {

    this.store = store; OnListChangedCallback<User> cb = OnListChangedCallback.delegateTo(this); hook.addOnCreate(() -> store.addOnListChanged(cb)); hook.addOnDestroy(() -> store.removeOnListChanged(cb)); } @Override public void onBindViewHolder(ViewHolder holder, int position) { User user = store.getItemAt(position); ... } @Override public int getItemCount() { return store.getItemCount(); }
  48. Flux: View ActionʹॲཧΛҕৡ͢Δ @Inject UserSearchAction userSearchAction; // SearchInputFragment @Override public

    void onViewCreated(View view, …) { binding.searchButton.setOnClickListener(v -> { hideKeyboard(binding.searchInputText.getWindowToken()); Optional.ofNullable(binding.searchInputText.getText()) .map(Editable::toString) .filter(it -> !it.isEmpty()) .ifPresent(userSearchAction::findFollower); }); // SearchResultFragment @Override public void onLoadMore() { userSearchAction.findFollower( userSearchStore.getUserId(), userSearchStore.getNextPage()); }
  49. Flux: View Tips: ObservableXXͷ୅ΘΓʹRxΛ࢖͏ import com.trello.rxlifecycle.components.support.RxFragment; public class SearchResultFragment extends

    RxFragment { @Inject UserSearchStore userSearchStore; @Override public void onViewCreated(View view, Bundle savedInstanceState) { ... userSearchStore.state() .map(it -> it == LoadingState.LOADING) .compose(bindToLifecycle()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(binding::setIsLoading); }
  50. Conclusion Pros :) • Viewؒͷґଘ͕ܹݮͯ͠ɺѹ౗తײँʂ • ໾ׂ͕໌֬ͳͷͰ։ൃऀͷ࣮૷͕౷Ұ͞Ε΍͍͢ • ୯ํ޲ͳͷͰίʔυ͕௥͍΍͍͢

  51. Conclusion Cons :( • γϯϓϧͳػೳͩͱएׯ৑௕ʹײ͡Δͱ͖΋... • ղ์ϛεΔͱଈϝϞϦϦʔΫʘ(^o^)ʗ • جຊτϥΠˍΤϥʔ (ʀ´∀ʆ)

  52. Let's Flux de Relax :)