Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Flux de Relax :)

Slide 4

Slide 4 text

What is Flux?

Slide 5

Slide 5 text

Facebook Flux Architecture IUUQTGBDFCPPLHJUIVCJPqVYEPDTPWFSWJFXIUNM “Data in a Flux application flows in a single direction”

Slide 6

Slide 6 text

Facebook Flux Architecture IUUQTHJUIVCDPNGBDFCPPLqVY

Slide 7

Slide 7 text

Facebook Flux Architecture IUUQTHJUIVCDPNGBDFCPPLqVY Observerύλʔϯ

Slide 8

Slide 8 text

Why Flux?

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Flux Architecture Sample

Slide 13

Slide 13 text

Flux: Dispatcher

Slide 14

Slide 14 text

Flux: Dispatcher N:1 1:N

Slide 15

Slide 15 text

Flux: Dispatcher /* Store */ Dispatcher.register(function(payload) { switch(payload.actionType) { case 'foo': ... = payload.data // Do something } } /* Action */ Dispatcher.dispatch({ actionType: 'foo', payload: 'value' }); ຊՈFluxͷ࣮૷Λࢀߟʹͯ͠ΈΔ

Slide 16

Slide 16 text

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; } } }

Slide 17

Slide 17 text

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ͩͱܕม׵͕ඞཁ :(

Slide 18

Slide 18 text

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ͰΑ͘Ͷ͐ʁ

Slide 19

Slide 19 text

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); } }

Slide 20

Slide 20 text

Flux: Action

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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); }, ...); }

Slide 27

Slide 27 text

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(...) }

Slide 28

Slide 28 text

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(...); }

Slide 29

Slide 29 text

Flux: Store

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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Λొ࿥͢Δ

Slide 36

Slide 36 text

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Λొ࿥͢Δ

Slide 37

Slide 37 text

Flux: Store CallbackॲཧͰࣗ਎ͷঢ়ଶΛߋ৽͢Δ private final ObservableField state = new ObservableField<>(LoadingState.LOADABLE); @Subscribe(threadMode = ThreadMode.MAIN) public void on(SearchLoadingStateChangedEvent event) { state.set(event.state); }

Slide 38

Slide 38 text

Flux: Store ঢ়ଶมߋΛ௨஌͢ΔͨΊͷϝιουΛެ։͢Δ private final ObservableField state = new ObservableField<>(LoadingState.LOADABLE); public Disposer addOnLoadingStateChanged( OnFieldChangedCallback cb) { state.addOnPropertyChangedCallback(cb); return Disposers.from(() -> removeOnLoadingStateChanged(cb)); } public void removeOnLoadingStateChanged( OnFieldChangedCallback cb) { state.removeOnPropertyChangedCallback(cb); }

Slide 39

Slide 39 text

Flux: Store Tips: ObservableXXͷ୅ΘΓʹRxΛ࢖͏ private final BehaviorSubject state = BehaviorSubject.create(LoadingState.LOADABLE); public Observable state() { return state.asObservable(); } @Subscribe(threadMode = ThreadMode.MAIN) public void on(SearchLoadingStateChangedEvent event) { state.onNext(event.state); }

Slide 40

Slide 40 text

Flux: View

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Flux: View Storeͷঢ়ଶʹԠͯ͡AdapterΛߋ৽͢Δ @Inject public UserSearchListAdapter(UserSearchStore store, ActivityLifecycleHook hook) { this.store = store; OnListChangedCallback 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(); }

Slide 48

Slide 48 text

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()); }

Slide 49

Slide 49 text

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); }

Slide 50

Slide 50 text

Conclusion Pros :) • Viewؒͷґଘ͕ܹݮͯ͠ɺѹ౗తײँʂ • ໾ׂ͕໌֬ͳͷͰ։ൃऀͷ࣮૷͕౷Ұ͞Ε΍͍͢ • ୯ํ޲ͳͷͰίʔυ͕௥͍΍͍͢

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

Let's Flux de Relax :)