Upgrade to Pro — share decks privately, control downloads, hide ads and more …

flux with kotlin (Abema Dev Con 2016)

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on. →
Avatar for AAkira AAkira
October 15, 2016

flux with kotlin (Abema Dev Con 2016)

Presented at Abema developer conference 2016
Introduced flux architecture with Kotlin and RxJava.

conference page : http://developer.abema.io/
video in Japanese : https://abemafresh.tv/tech-conference/47693

Avatar for AAkira

AAkira

October 15, 2016

More Decks by AAkira

Other Decks in Technology

Transcript

  1. Released map M1 2012-04-12 M11 2015-03-19 M14 2015-10-01 1.0-beta4 2015-12-22

    M13 2015-09-16 1.0 2016-02-16 1.0-RC 2016-02-04 2016-01-21 Release 2015-04 開発開始 kotlin FRESH
  2. View Action Dispatcher Store Repository DB WEB view(flux) data OKHttp(Retrofit)

    SQLBrite Observable<T> Subject Observable<T> Observable<T>
  3. Using libraries • Dagger2 • RxJava(RxKotlin) • RxAndroid • RxBinding

    • SQLBrite • RxPreferences • RxLifecycle => DI => Reactive Extensions => RxJava bindings for Android => binding APIs for Android's UI widgets => introduces reactive stream to SQLite => introduces reactive stream to Preferences => handles Android LifeCycles
  4. Action class ArchiveAction(private val dispatcher: ArchiveDispatcher, private val programRepository: ProgramRepository)

    { fun refresh() { programRepository.getProgramsArchive(count = 20, offset = 0) .subscribe({ dispatcher.archiveDao.replaceAll(it) }, { dispatcher.onError(this, it, recoverAction = { refresh(screenId) }) }) } }
  5. Action class ArchiveAction(private val dispatcher: ArchiveDispatcher, private val programRepository: ProgramRepository)

    { fun refresh() { programRepository.getProgramsArchive(count = 20, offset = 0) .subscribe({ dispatcher.archiveDao.replaceAll(it) }, { dispatcher.onError(this, it, recoverAction = { refresh(screenId) }) }) } } *OKFDU%JTQBUDIFSBOE3FQPTJUPSZ
  6. Action class ArchiveAction(private val dispatcher: ArchiveDispatcher, private val programRepository: ProgramRepository)

    { fun refresh() { programRepository.getProgramsArchive(count = 20, offset = 0) .subscribe({ dispatcher.archiveDao.replaceAll(it) }, { dispatcher.onError(this, it, recoverAction = { refresh(screenId) }) }) } } $BMM3FQPTJUPSZ
  7. Repository class ProgramRepository(private val programService: ProgramService) { fun getProgramsArchive(count: Int?

    = null, offset: Int? = null) = programService.getPrograms(count = count, offset = offset, status = "archive", orderBy = "endAt") .subscribeOn(Schedulers.io()) .map { it.data.nullSafeMap(::toProgram) } }
  8. Repository class ProgramRepository(private val programService: ProgramService) { fun getProgramsArchive(count: Int?

    = null, offset: Int? = null) = programService.getPrograms(count = count, offset = offset, status = "archive", orderBy = "endAt") .subscribeOn(Schedulers.io()) .map { it.data.nullSafeMap(::toProgram) } } *OKFDU4FSWJDF
  9. Repository class ProgramRepository(private val programService: ProgramService) { fun getProgramsArchive(count: Int?

    = null, offset: Int? = null) = programService.getPrograms(count = count, offset = offset, status = "archive", orderBy = "endAt") .subscribeOn(Schedulers.io()) .map { it.data.nullSafeMap(::toProgram) } } $BMM4FSWJDF
  10. WEB (Service) interface ProgramService { @GET("/programs") fun getPrograms(@Query("channelId") channelId: String?

    = null, @Query("count") count: Int?, @Query("orderBy") orderBy: String? = null, @Query("status") status: String? = null, ……, ……, ): Observable<ApiResponse<List<ApiProgram>>> }
  11. WEB (Service) 3FUVSOPCTFSWBCMF 3FUSPpU 0,)UUQ interface ProgramService { @GET("/programs") fun

    getPrograms(@Query("channelId") channelId: String? = null, @Query("count") count: Int?, @Query("orderBy") orderBy: String? = null, @Query("status") status: String? = null, ……, ……, ): Observable<ApiResponse<List<ApiProgram>>> }
  12. Action / Dispatcher class ArchiveAction(private val dispatcher: ArchiveDispatcher, private val

    programRepository: ProgramRepository) { fun refresh() { programRepository.getProgramsArchive(count = 20, offset = 0) .subscribe({ dispatcher.archiveDao.replaceAll(it) }, { dispatcher.onError(this, it, recoverAction = { refresh() }) }) } }
  13. Action / Dispatcher class ArchiveAction(private val dispatcher: ArchiveDispatcher, private val

    programRepository: ProgramRepository) { fun refresh() { programRepository.getProgramsArchive(count = 20, offset = 0) .subscribe({ dispatcher.archiveDao.replaceAll(it) }, { dispatcher.onError(this, it, recoverAction = { refresh() }) }) } } 6QEBUFEBUBCBTF
  14. Store class ArchiveStore(private val dispatcher: ArchiveDispatcher) { fun programs() =

    dispatcher.archiveDao.selectAll().subscribeOn(Schedulers.io()) }
  15. Store *OKFDU%JTQBUDIFS class ArchiveStore(private val dispatcher: ArchiveDispatcher) { fun programs()

    = dispatcher.archiveDao.selectAll().subscribeOn(Schedulers.io()) }
  16. Store 0CTFSWF42- 42-#SJUF class ArchiveStore(private val dispatcher: ArchiveDispatcher) { fun

    programs() = dispatcher.archiveDao.selectAll().subscribeOn(Schedulers.io()) }
  17. View class ArchiveFragment { @Inject lateinit var archiveAction: ArchiveAction @Inject

    lateinit var archiveStore: ArchiveStore override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) archiveStore.programs() .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { adapter.clear() it.forEach { … } // add to adapter adapter.notifyDataSetChanged() } savedInstanceState ?: archiveAction.refresh() }
  18. View class ArchiveFragment { @Inject lateinit var archiveAction: ArchiveAction @Inject

    lateinit var archiveStore: ArchiveStore override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) archiveStore.programs() .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { adapter.clear() it.forEach { … } // add to adapter adapter.notifyDataSetChanged() } savedInstanceState ?: archiveAction.refresh() } *OKFDU"DUJPOBOE4UPSF
  19. View class ArchiveFragment { @Inject lateinit var archiveAction: ArchiveAction @Inject

    lateinit var archiveStore: ArchiveStore override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) archiveStore.programs() .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { adapter.clear() it.forEach { … } // add to adapter adapter.notifyDataSetChanged() } savedInstanceState ?: archiveAction.refresh() } $BMMSFGSFTIBDUJPO
  20. View class ArchiveFragment { @Inject lateinit var archiveAction: ArchiveAction @Inject

    lateinit var archiveStore: ArchiveStore override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) archiveStore.programs() .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { adapter.clear() it.forEach { … } // add to adapter adapter.notifyDataSetChanged() } savedInstanceState ?: archiveAction.refresh() } 0CTFSWFTUPSF
  21. View class ArchiveFragment { @Inject lateinit var archiveAction: ArchiveAction @Inject

    lateinit var archiveStore: ArchiveStore override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) archiveStore.programs() .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { adapter.clear() it.forEach { … } // add to adapter adapter.notifyDataSetChanged() } savedInstanceState ?: archiveAction.refresh() } 3Y"OESPJE
  22. View class ArchiveFragment { @Inject lateinit var archiveAction: ArchiveAction @Inject

    lateinit var archiveStore: ArchiveStore override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) archiveStore.programs() .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { adapter.clear() it.forEach { … } // add to adapter adapter.notifyDataSetChanged() } savedInstanceState ?: archiveAction.refresh() } 3Y-JGFDZDMF
  23. View class ArchiveFragment { @Inject lateinit var archiveAction: ArchiveAction @Inject

    lateinit var archiveStore: ArchiveStore override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) archiveStore.programs() .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { adapter.clear() it.forEach { … } // add to adapter adapter.notifyDataSetChanged() } savedInstanceState ?: archiveAction.refresh() }
  24. View class ArchiveFragment { @Inject lateinit var archiveAction: ArchiveAction @Inject

    lateinit var archiveStore: ArchiveStore override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) archiveStore.programs() .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { adapter.clear() it.forEach { … } // add to adapter adapter.notifyDataSetChanged() } savedInstanceState ?: archiveAction.refresh() } Why don’t you implement onError
  25. Action / Dispatcher class ArchiveAction(private val dispatcher: ArchiveDispatcher, private val

    programRepository: ProgramRepository) { fun refresh() { programRepository.getProgramsArchive(count = 20, offset = 0) .subscribe({ dispatcher.archiveDao.replaceAll(it) }, { dispatcher.onError(this, it, recoverAction = { refresh() }) }) } } 4FOEFSSPS open class CommonDispatcher(val provider: DispatcherProvider) { fun errors() = provider.errorDispatcher.errorObservable fun onError(action: Any, cause: Throwable, extras: Any? = null, recoverAction: (() -> Unit)? = null) = provider.errorDispatcher.onError(action, cause, extras, recoverAction) }
  26. Action / Dispatcher class ArchiveAction(private val dispatcher: ArchiveDispatcher, private val

    programRepository: ProgramRepository) { fun refresh() { programRepository.getProgramsArchive(count = 20, offset = 0) .subscribe({ dispatcher.archiveDao.replaceAll(it) }, { dispatcher.onError(this, it, recoverAction = { refresh() }) }) } } open class CommonDispatcher(val provider: DispatcherProvider) { fun errors() = provider.errorDispatcher.errorObservable fun onError(action: Any, cause: Throwable, extras: Any? = null, recoverAction: (() -> Unit)? = null) = provider.errorDispatcher.onError(action, cause, extras, recoverAction) } $PNNPO%JTQBUDIFS
  27. Action / Dispatcher class ArchiveAction(private val dispatcher: ArchiveDispatcher, private val

    programRepository: ProgramRepository) { fun refresh() { programRepository.getProgramsArchive(count = 20, offset = 0) .subscribe({ dispatcher.archiveDao.replaceAll(it) }, { dispatcher.onError(this, it, recoverAction = { refresh() }) }) } } open class CommonDispatcher(val provider: DispatcherProvider) { fun errors() = provider.errorDispatcher.errorObservable fun onError(action: Any, cause: Throwable, extras: Any? = null, recoverAction: (() -> Unit)? = null) = provider.errorDispatcher.onError(action, cause, extras, recoverAction) } *OMJOF'VODUJPOTCZ,PUMJO
  28. View class ArchiveFragment { @Inject lateinit var archiveStore: ArchiveStore override

    fun onResume() { super.onResume() onAirStore.errors() .filter { it.action is ArchiveAction } .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { if (it.kind == ErrorEvent.Kind.NETWORK) { showSnackBar(){ it.recoverAction?.invoke() } } } } }
  29. View class ArchiveFragment { @Inject lateinit var archiveStore: ArchiveStore override

    fun onResume() { super.onResume() onAirStore.errors() .filter { it.action is ArchiveAction } .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { if (it.kind == ErrorEvent.Kind.NETWORK) { showSnackBar(){ it.recoverAction?.invoke() } } } } } 4VCTDSJCFJOPO3FTVNF
  30. View class ArchiveFragment { @Inject lateinit var archiveStore: ArchiveStore override

    fun onResume() { super.onResume() onAirStore.errors() .filter { it.action is ArchiveAction } .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { if (it.kind == ErrorEvent.Kind.NETWORK) { showSnackBar(){ it.recoverAction?.invoke() } } } } } 'JMUFSPOMZBSDIJWFBDUJPO %* 
  31. View class ArchiveFragment { @Inject lateinit var archiveStore: ArchiveStore override

    fun onResume() { super.onResume() onAirStore.errors() .filter { it.action is ArchiveAction } .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { if (it.kind == ErrorEvent.Kind.NETWORK) { showSnackBar(){ it.recoverAction?.invoke() } } } } } 3FUSZSFDPWFS"DUJPOJGVTFSTDMJDLBTOBDLCBS
  32. Like event bus class ArchiveDispatcher(provider: DispatcherProvider) { val statusObservable =

    SerializedSubject(BehaviorSubject.create<Status>()) } class ArchiveAction(private val dispatcher: ArchiveDispatcher) { fun updateStatus(status: Status) { dispatcher.statusObservable.onNext(status) } } class ArchiveStore(private val dispatcher: ArchiveDispatcher) { fun status() = dispatcher.statusObservable }
  33. Like event bus class ArchiveDispatcher(provider: DispatcherProvider) { val statusObservable =

    SerializedSubject(BehaviorSubject.create<Status>()) } class ArchiveAction(private val dispatcher: ArchiveDispatcher) { fun updateStatus(status: Status) { dispatcher.statusObservable.onNext(status) } } class ArchiveStore(private val dispatcher: ArchiveDispatcher) { fun status() = dispatcher.statusObservable }
  34. Like event bus class ArchiveDispatcher(provider: DispatcherProvider) { val statusObservable =

    SerializedSubject(BehaviorSubject.create<Status>()) } class ArchiveAction(private val dispatcher: ArchiveDispatcher) { fun updateStatus(status: Status) { dispatcher.statusObservable.onNext(status) } } class ArchiveStore(private val dispatcher: ArchiveDispatcher) { fun status() = dispatcher.statusObservable }
  35. Like event bus class ArchiveDispatcher(provider: DispatcherProvider) { val statusObservable =

    SerializedSubject(BehaviorSubject.create<Status>()) } class ArchiveAction(private val dispatcher: ArchiveDispatcher) { fun updateStatus(status: Status) { dispatcher.statusObservable.onNext(status) } } class ArchiveStore(private val dispatcher: ArchiveDispatcher) { fun status() = dispatcher.statusObservable }
  36. Like event bus class ArchiveFragment { @Inject lateinit var archiveStore:

    ArchiveStore override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) archiveStore.status() .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { when(it) { HOGE -> textView.text = "hoge" FOO -> textView.text = "foo" BAR -> textView.text = "bar" } } }
  37. Like event bus class ArchiveFragment { @Inject lateinit var archiveStore:

    ArchiveStore override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) archiveStore.status() .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { when(it) { HOGE -> textView.text = "hoge" FOO -> textView.text = "foo" BAR -> textView.text = "bar" } } }
  38. Connect observables refreshButton.clicks() .mergeWith(recyclerView.pull()) .withLatestFrom(archiveStore.programs()) { void, programs -> programs

    } .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { // add to adapter }
  39. Connect observables 3Y#JOEJOH refreshButton.clicks() .mergeWith(recyclerView.pull()) .withLatestFrom(archiveStore.programs()) { void, programs ->

    programs } .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { // add to adapter }
  40. Connect observables .FSHFDVTUPNPCTFSWBCMF refreshButton.clicks() .mergeWith(recyclerView.pull()) .withLatestFrom(archiveStore.programs()) { void, programs ->

    programs } .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { // add to adapter }
  41. Connect observables $POOFDUJUFNTGSPNTUPSF refreshButton.clicks() .mergeWith(recyclerView.pull()) .withLatestFrom(archiveStore.programs()) { void, programs ->

    programs } .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { // add to adapter }
  42. Connect observables "EEUPJUFNTJOTVCTDSJCF refreshButton.clicks() .mergeWith(recyclerView.pull()) .withLatestFrom(archiveStore.programs()) { void, programs ->

    programs } .observeOn(AndroidSchedulers.mainThread()) .bindToLifecycle(this) .subscribe { // add to adapter }
  43. • combineLatest, mergeWith, zipWith • map, switchMap, concatMap • scan,

    reduce • interval • filter, skip, distinctUntilChanged • etc… Other operators used in FRESH