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

flux with kotlin (Abema Dev Con 2016)

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

AAkira

October 15, 2016
Tweet

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