Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Flux de Relax :)
Search
Masaki Ogata
August 07, 2016
Programming
13
61k
Flux de Relax :)
Flux implementation for Android
Masaki Ogata
August 07, 2016
Tweet
Share
More Decks by Masaki Ogata
See All by Masaki Ogata
Jetpack Composeで始めるServer Cache State
ogaclejapan
2
160
AbemaTVを支えるアプリの優しさ / abematv_devcon_2017
ogaclejapan
3
6.4k
Architecture Components - Lifecycle library
ogaclejapan
3
2.3k
How to keep data between orientation changes
ogaclejapan
7
2.5k
Your app name
ogaclejapan
1
2.8k
Dagger2 has been released!
ogaclejapan
3
3.2k
Other Decks in Programming
See All in Programming
今からはじめるAndroidアプリ開発 2024 / DevFest 2024
star_zero
0
1k
競技プログラミングへのお誘い@阪大BOOSTセミナー
kotamanegi
0
350
Асинхронность неизбежна: как мы проектировали сервис уведомлений
lamodatech
0
600
複雑な仕様に立ち向かうアーキテクチャ
myohei
0
170
採用事例の少ないSvelteを選んだ理由と それを正解にするためにやっていること
oekazuma
2
1k
nekko cloudにおけるProxmox VE利用事例
irumaru
3
420
103 Early Hints
sugi_0000
1
220
rails statsで大解剖 🔍 “B/43流” のRailsの育て方を歴史とともに振り返ります
shoheimitani
2
930
Stackless и stackful? Корутины и асинхронность в Go
lamodatech
0
600
StarlingMonkeyを触ってみた話 - 2024冬
syumai
3
270
テスト自動化失敗から再挑戦しチームにオーナーシップを委譲した話/STAC2024 macho
ma_cho29
1
1.3k
命名をリントする
chiroruxx
1
380
Featured
See All Featured
Optimizing for Happiness
mojombo
376
70k
Git: the NoSQL Database
bkeepers
PRO
427
64k
Bootstrapping a Software Product
garrettdimon
PRO
305
110k
[RailsConf 2023] Rails as a piece of cake
palkan
53
5k
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
Build The Right Thing And Hit Your Dates
maggiecrowley
33
2.4k
Keith and Marios Guide to Fast Websites
keithpitt
410
22k
A Tale of Four Properties
chriscoyier
157
23k
Gamification - CAS2011
davidbonilla
80
5.1k
The Power of CSS Pseudo Elements
geoffreycrofte
73
5.4k
We Have a Design System, Now What?
morganepeng
51
7.3k
YesSQL, Process and Tooling at Scale
rocio
169
14k
Transcript
Flux de Relax :) ANDROID ALLSTARS #2 @dots. Masaki Ogata
About me Masaki Ogata ogaclejapan CyberAgent, Inc. / AbemaTV, Inc.
@ogaclejapan
Flux de Relax :)
What is Flux?
Facebook Flux Architecture IUUQTGBDFCPPLHJUIVCJPqVYEPDTPWFSWJFXIUNM “Data in a Flux application flows
in a single direction”
Facebook Flux Architecture IUUQTHJUIVCDPNGBDFCPPLqVY
Facebook Flux Architecture IUUQTHJUIVCDPNGBDFCPPLqVY Observerύλʔϯ
Why Flux?
Why Flux? ΞϓϦέʔγϣϯͷ։ൃͰ Viewͷঢ়ଶཧ͕Ұ൪͍͠ :(
Why Flux? AbemaTVͰඞཁʹͳΔViewͷঢ়ଶཧ: Cast ՝ۚ CM ࢹௌ༧ ΦϯσϚϯυ ը࣭ ϑΟϥʔ
etc…
Sample code https://github.com/ogaclejapan/ FluxArchitectureSample *OQVU'SBHNFOU 3FTVMU'SBHNFOU
Flux Architecture Sample
Flux: Dispatcher
Flux: Dispatcher N:1 1:N
Flux: Dispatcher /* Store */ Dispatcher.register(function(payload) { switch(payload.actionType) { case
'foo': ... = payload.data // Do something } } /* Action */ Dispatcher.dispatch({ actionType: 'foo', payload: 'value' }); ຊՈFluxͷ࣮Λࢀߟʹͯ͠ΈΔ
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; } } }
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ͩͱܕม͕ඞཁ :(
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ͰΑ͘Ͷ͐ʁ
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); } }
Flux: Action
Flux: Action Actionͷσʔλϑϩʔ ٩(•౪• ٩)
Flux: Action View͔ΒͷೖྗʹΑΓσʔλ͕ྲྀΕͯ͘Δ
Flux: Action σʔλιʔεʹඞཁͱͳΔσʔλΛऔΓʹߦ͘
Flux: Action σʔλ͕ू·ͬͨΒDispatcherσʔλΛྲྀ͢
Flux: Action Web, DB, DevicesΛ֎෦αʔϏεͱͯ͠ଊ͑Δ
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); }, ...); }
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(...) }
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(...); }
Flux: Store
Flux: Store Storeͷσʔλϑϩʔ ₍₍ (ง ˙ω˙)ว ⁾⁾
Flux: Store σʔλΛड͚औΔͨΊʹCallbackΛొ͢Δ
Flux: Store Action͔ΒσʔλΛྲྀ͢ͱCallbackྲྀΕͯ͘Δ
Flux: Store σʔλΛड͚औͬͨΒStoreσʔλΛߋ৽͢Δ
Flux: Store σʔλΛߋ৽ͨ͠ΒViewมߋΛ௨͢Δ
Flux: Store @ActivityScope public class UserSearchStore { @Inject public UserSearchStore(Dispatcher
dispatcher, ActivityLifecycleHook hook) { hook.addOnCreate(() -> dispatcher.register(this)); hook.addOnDestroy(() -> dispatcher.unregister(this)); } DispatcherCallbackΛొ͢Δ
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)); } DispatcherCallbackΛొ͢Δ
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); }
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); }
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); }
Flux: View
Flux: View Viewͷσʔλϑϩʔ ٩(๑´3ʆ๑)۶
Flux: View σʔλΛड͚औΔͨΊStoreCallbackΛొ͢Δ
Flux: View σʔλ͕ߋ৽͞ΕͨΒCallback͕ݺΕΔ
Flux: View σʔλΛड͚औͬͨΒը໘Λߋ৽͢Δ
Flux: View ৽ͨͳೖྗ͕ൃੜͨ͠ΒActionσʔλΛྲྀ͢
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); }
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(); }
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()); }
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); }
Conclusion Pros :) • Viewؒͷґଘ͕ܹݮͯ͠ɺѹతײँʂ • ׂ͕໌֬ͳͷͰ։ൃऀͷ࣮͕౷Ұ͞Ε͍͢ • ୯ํͳͷͰίʔυ͕͍͍͢
Conclusion Cons :( • γϯϓϧͳػೳͩͱएׯʹײ͡Δͱ͖... • ղ์ϛεΔͱଈϝϞϦϦʔΫʘ(^o^)ʗ • جຊτϥΠˍΤϥʔ (ʀ´∀ʆ)
Let's Flux de Relax :)