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
230
AbemaTVを支えるアプリの優しさ / abematv_devcon_2017
ogaclejapan
3
6.6k
Architecture Components - Lifecycle library
ogaclejapan
3
2.5k
How to keep data between orientation changes
ogaclejapan
7
2.5k
Your app name
ogaclejapan
1
2.9k
Dagger2 has been released!
ogaclejapan
3
3.2k
Other Decks in Programming
See All in Programming
Blazing Fast UI Development with Compose Hot Reload (droidcon New York 2025)
zsmb
1
290
iOS 26にアップデートすると実機でのHot Reloadができない?
umigishiaoi
0
130
脱Riverpod?fqueryで考える、TanStack Queryライクなアーキテクチャの可能性
ostk0069
0
140
テストから始めるAgentic Coding 〜Claude Codeと共に行うTDD〜 / Agentic Coding starts with testing
rkaga
12
4.4k
PipeCDのプラグイン化で目指すところ
warashi
1
270
生成AI時代のコンポーネントライブラリの作り方
touyou
1
210
データの民主化を支える、透明性のあるデータ利活用への挑戦 2025-06-25 Database Engineering Meetup#7
y_ken
0
360
Azure AI Foundryではじめてのマルチエージェントワークフロー
seosoft
0
170
イベントストーミング図からコードへの変換手順 / Procedure for Converting Event Storming Diagrams to Code
nrslib
2
810
なぜ「共通化」を考え、失敗を繰り返すのか
rinchoku
1
650
LT 2025-06-30: プロダクトエンジニアの役割
yamamotok
0
760
Composerが「依存解決」のためにどんな工夫をしているか #phpcon
o0h
PRO
1
260
Featured
See All Featured
Build your cross-platform service in a week with App Engine
jlugia
231
18k
The Art of Programming - Codeland 2020
erikaheidi
54
13k
The Invisible Side of Design
smashingmag
301
51k
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
Git: the NoSQL Database
bkeepers
PRO
430
65k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
248
1.3M
Build The Right Thing And Hit Your Dates
maggiecrowley
36
2.8k
Product Roadmaps are Hard
iamctodd
PRO
54
11k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
45
7.5k
YesSQL, Process and Tooling at Scale
rocio
173
14k
Optimising Largest Contentful Paint
csswizardry
37
3.3k
Fireside Chat
paigeccino
37
3.5k
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 :)