Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Flux de Relax :)
Search
Masaki Ogata
August 07, 2016
Programming
62k
13
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Flux de Relax :)
Flux implementation for Android
Masaki Ogata
August 07, 2016
More Decks by Masaki Ogata
See All by Masaki Ogata
Jetpack Composeで始めるServer Cache State
ogaclejapan
2
330
AbemaTVを支えるアプリの優しさ / abematv_devcon_2017
ogaclejapan
3
6.9k
Architecture Components - Lifecycle library
ogaclejapan
3
2.9k
How to keep data between orientation changes
ogaclejapan
7
2.7k
Your app name
ogaclejapan
1
3k
Dagger2 has been released!
ogaclejapan
3
3.4k
Other Decks in Programming
See All in Programming
パズルゲームの作り方 / how to make puzzle games
kaityo256
PRO
2
300
作って学ぶSpring BootのAuto Configuration / JJUG Night Seminar 2026-10-19
rshindo
0
180
[2026-10-14](C)Re:ゼロから始める異世界生活〜始まりの終わりと終わりの始まり〜
tosite
0
110
AI主導のプログラミング学習アプリを作ってみた
econanringo
0
180
Lambda durable functionsでHuman in the Loopを楽に…楽に……
har1101
0
110
dotNET_vulnclicker_intro.pdf
deka_morita
0
150
R8 の設定見直しでアプリサイズ削減
andpad
0
220
Domain-Driven Transformation
hschwentner
2
2.3k
RAG の “R” を Swift で覗いてみる 〜「意味から探す」検索の仕組み〜
nao_randd
0
170
AWS CDKのカスタムリソースでContinuum(旧Security Agent)を実装した話 | Implementing Continuum (formerly Security Agent) via AWS CDK Custom Resource
akihisaikeda
2
150
AWSに止められる覚悟してますか?
morizo_1984
2
540
ChromeでローカルLLM
ryu1013
0
140
Featured
See All Featured
Building the Perfect Custom Keyboard
takai
2
910
Music & Morning Musume
bryan
48
7.4k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6.1k
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
260
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
203
77k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
460
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
380
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
360
It's Worth the Effort
3n
188
29k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.6k
Abbi's Birthday
coloredviolet
4
10k
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 :)