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.6k
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
中級グラフィックス入門~効率的なメッシュレット描画~
projectasura
4
2.4k
Advanced Micro Frontends: Multi Version/ Framework Scenarios
manfredsteyer
PRO
0
150
テスターからテストエンジニアへ ~新米テストエンジニアが歩んだ9ヶ月振り返り~
non0113
2
250
Claude Code と OpenAI o3 で メタデータ情報を作る
laket
0
110
Understanding Kotlin Multiplatform
l2hyunwoo
0
250
GUI操作LLMの最新動向: UI-TARSと関連論文紹介
kfujikawa
0
390
ZeroETLで始めるDynamoDBとS3の連携
afooooil
0
150
なぜあなたのオブザーバビリティ導入は頓挫するのか
ryota_hnk
5
560
0から始めるモジュラーモノリス-クリーンなモノリスを目指して
sushi0120
0
250
実践 Dev Containers × Claude Code
touyu
1
120
Android 15以上でPDFのテキスト検索を爆速開発!
tonionagauzzi
0
180
新しいモバイルアプリ勉強会(仮)について
uetyo
1
250
Featured
See All Featured
GitHub's CSS Performance
jonrohan
1031
460k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
18
1k
The Power of CSS Pseudo Elements
geoffreycrofte
77
5.9k
Making Projects Easy
brettharned
117
6.3k
Building Adaptive Systems
keathley
43
2.7k
Product Roadmaps are Hard
iamctodd
PRO
54
11k
Scaling GitHub
holman
461
140k
Adopting Sorbet at Scale
ufuk
77
9.5k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
248
1.3M
jQuery: Nuts, Bolts and Bling
dougneiner
63
7.8k
We Have a Design System, Now What?
morganepeng
53
7.7k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
667
120k
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 :)