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

Blocks and Managers - ADC Brazil - 2017

Blocks and Managers - ADC Brazil - 2017

Nubank's reactive pattern for android

Thales Machado

August 29, 2017
Tweet

More Decks by Thales Machado

Other Decks in Technology

Transcript

  1. • ~80 Activities • ~70 Fragments • 5k Unit tests

    • 800 Instrumental tests INTRODUCTION Nubank Jan 2017 Jan 2015 350 k 100 k Jan 2016 200 k 1 2 3 5 6 8
  2. • Model - the data layer. Responsible for handling the

    business logic and communication with the network and database layers. • View - the UI layer. Displays the data and notifies the Presenter about user actions. • Presenter - retrieves the data from the Model, applies the UI logic and manages the state of the View, decides what to display and reacts to user input notifications from the View. MVP Roles
  3. class FeedActivity(): Activity, FeedView { override fun showFeed(feedList: List<FeedItem>){ ...

    } override fun onRefreshListener() = refreshBtn.clicks() } MVP View Implementation
  4. class FeedPresenter(feedRepository: FeedRepository, feedView: FeedView) { init { feedView.onRefreshListener.subscribe {

    ... } feedRepository.getFeed().subscribe { feedView.showFeed(it) } } } MVP Presenter Implementation
  5. • Model - the data layer. Responsible for handling the

    business logic and communication with the network and database layers. • View - the UI layer. Listen to a stream of data and notifies the ViewModel about user actions. • ViewModel - Abstracts the data source and reacts to user input notifications from the View. Its like a Model for the View (Like the name suggests). MVVM Roles
  6. <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="com.nubank.FeedViewModel"/> </data>

    <TextView android:text=“@{viewModel.feedDataObservable.listSize}”/> <Button android:onClick="@{() -> viewModel.refresh()}"/> </layout> MVVM View Implementation (Data binding way)
  7. <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="com.nubank.FeedViewModel"/> </data>

    <TextView android:text=“@{viewModel.feedDataObservable.listSize}”/> <Button android:onClick="@{() -> viewModel.refresh()}"/> </layout> MVVM View Implementation (Data binding way)
  8. <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="com.nubank.FeedViewModel"/> </data>

    <TextView android:text=“@{viewModel.feedDataObservable.listSize}”/> <Button android:onClick="@{() -> viewModel.refresh()}"/> </layout> MVVM View Implementation (Data binding way)
  9. class FeedViewModel(private val feedRepository: FeedRepository) { val feedObservable = ObservableField<FeedData>()

    init { feedRepository .getFeed() .map { feedData(it) } .subscribe { feedObservable.set(it) } } fun refresh() { ... } } MVVM ViewModel Implementation (Data binding way)
  10. MVVM ViewModel Implementation (Data binding way) class FeedViewModel(private val feedRepository:

    FeedRepository) { val feedObservable = ObservableField<FeedData>() init { feedRepository .getFeed() .map { feedData(it) } .subscribe { feedObservable.set(it) } } fun refresh() { ... } }
  11. class FeedActivity(): Activity { override fun onResume() { viewModel.feedDataSubject .subscribe(this::updateView)

    } fun onRefreshListener() = refreshBtn.clicks() } MVVM View Implementation (Reactive way)
  12. MVVM ViewModel Implementation (Reactive Way) class FeedViewModel(private val feedRepository: FeedRepository)

    { val feedDataSubject = PublishSubject.create<FeedData>() init { feedView.onRefreshListener.subscribe { ... } feedRepository .getFeed() .map { feedData(it) } .subscribe { feedDataSubject.onNext(it) } } }
  13. MVVM ViewModel Implementation (Reactive Way) class FeedViewModel(private val feedRepository: FeedRepository)

    { val feedDataSubject = PublishSubject.create<FeedData>() init { feedView.onRefreshListener.subscribe { ... } feedRepository .getFeed() .map { feedData(it) } .subscribe { feedDataSubject.onNext(it) } } }
  14. BLOCKS Problem with original MVVM pattern (show me the code)

    class FeedViewModel(private val feedRepository: FeedRepository) { val feedObservable = ObservableField<FeedData>() init { feedView.onRefreshListener.subscribe { ... } feedRepository .getFeed() .map { feedData(it) } .subscribe { feedObservable.set(it) } } }
  15. BLOCKS Problem with original MVVM pattern (show me the code)

    class FeedViewModel(private val feedRepository: FeedRepository) { val feedObservable = ObservableField<FeedData>() val feedContentObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() init { feedView.onRefreshListener.subscribe { ... } val hasValue = feedRepository.hasValue() feedContentObservable.set(hasValue) shimmerObservable.set(!hasValue) feedRepository .getFeed() .map { feedData(it) } .subscribe { feedObservable.set(it) } } }
  16. class FeedViewModel(private val feedRepository: FeedRepository) { val feedObservable = ObservableField<FeedData>()

    val feedContentObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() init { feedView.onRefreshListener.subscribe { ... } val hasValue = feedRepository.hasValue() feedContentObservable.set(hasValue) shimmerObservable.set(! hasValue) feedRepository .getFeed() .map { feedData(it) } .subscribe { feedObservable.set(it) } } }
  17. class FeedViewModel(private val feedRepository: FeedRepository) { val feedObservable = ObservableField<FeedData>()

    val feedContentObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() init { feedView.onRefreshListener.subscribe { ... } retryObservable.set(false) val hasValue = feedRepository.hasValue() feedContentObservable.set(hasValue) shimmerObservable.set(! hasValue) feedRepository .getFeed() .map { feedData(it) } .subscribe { feedObservable.set(it) }, { feedContentObservable.set(false) shimmerObservable.set(false) retryObservable.set(!hasValue) } }
  18. class FeedViewModel( private val feedRepository: FeedRepository, private val bannerRepository: BannerRepository

    ) { val feedObservable = ObservableField<FeedData>() val bannerObservable = ObservableField<BannerData>() val bannerShimmerObservable = ObservableField<BannerData>() val bannerRetryObservable = ObservableField<BannerData>() val feedContentObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() init { feedView.onRefreshListener.subscribe { ... } retryObservable.set(false) var hasValue = feedRepository.hasValue() feedContentObservable.set(hasValue) shimmerObservable.set(! hasValue) feedRepository .getFeed() .map { feedData(it) } .subscribe { feedObservable.set(it) shimmerObservable.set(false) retryObservable.set(false) hasValue = true }, { feedContentObservable.set(false) shimmerObservable.set(false) retryObservable.set(!hasValue) } bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() .map { bannerData(it) } .subscribe { bannerShimmerObservable.set(false) bannerRetryObservable.set(false) if (!hasValue) { bannerObservable.set(it) } else { (. show some toast) } }, { bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } } }
  19. class FeedViewModel( private val feedRepository: FeedRepository, private val bannerRepository: BannerRepository

    ) { val feedObservable = ObservableField<FeedData>() val bannerObservable = ObservableField<BannerData>() val bannerShimmerObservable = ObservableField<BannerData>() val bannerRetryObservable = ObservableField<BannerData>() val feedContentObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() var hasValue = feedRepository.hasValue() init { feedView.onRefreshListener.subscribe { ... } feedView.retryListener.subscribe { loadFeed() } feedView.bannerRetryListener.subscribe { loadBanners() } } fun loadFeed() { retryObservable.set(false) feedContentObservable.set(hasValue) shimmerObservable.set(! hasValue) feedRepository .getFeed() .map { feedData(it) } .subscribe { feedObservable.set(it) shimmerObservable.set(false) retryObservable.set(false) hasValue = true }, { feedContentObservable.set(false) shimmerObservable.set(false) retryObservable.set(!hasValue) } } fun loadBanners() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() .map { bannerData(it) } .subscribe { bannerShimmerObservable.set(false) bannerRetryObservable.set(false) if (!hasValue) { bannerObservable.set(it) } else { (. show some toast) } }, { bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } } }
  20. class FeedViewModel( private val feedRepository: FeedRepository, private val bannerRepository: BannerRepository

    ) { val feedObservable = ObservableField<FeedData>() val bannerObservable = ObservableField<BannerData>() val bannerShimmerObservable = ObservableField<BannerData>() val bannerRetryObservable = ObservableField<BannerData>() val feedContentObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() var hasValue = feedRepository.hasValue() init { feedView.onRefreshListener.subscribe { ... } feedView.retryListener.subscribe { loadFeed() } feedView.bannerRetryListener.subscribe { loadBanners() } } fun loadFeed() { retryObservable.set(false) feedContentObservable.set(hasValue) shimmerObservable.set(! hasValue) feedRepository .getFeed() .map { feedData(it) } .subscribe { feedObservable.set(it) shimmerObservable.set(false) retryObservable.set(false) hasValue = true }, { feedContentObservable.set(false) shimmerObservable.set(false) retryObservable.set(!hasValue) } } fun loadBanners() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() .map { bannerData(it) } .subscribe { bannerShimmerObservable.set(false) bannerRetryObservable.set(false) if (!hasValue) { bannerObservable.set(it) } else { (. show some toast) } }, { bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } } } And it goes on
  21. class FeedViewModel( private val feedRepository: FeedRepository, private val bannerRepository: BannerRepository

    ) { val feedObservable = ObservableField<FeedData>() val bannerObservable = ObservableField<BannerData>() val bannerShimmerObservable = ObservableField<BannerData>() val bannerRetryObservable = ObservableField<BannerData>() val feedContentObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() var hasValue = feedRepository.hasValue() init { feedView.onRefreshListener.subscribe { ... } feedView.retryListener.subscribe { loadFeed() } feedView.bannerRetryListener.subscribe { loadBanners() } } fun loadFeed() { retryObservable.set(false) feedContentObservable.set(hasValue) shimmerObservable.set(! hasValue) feedRepository .getFeed() .map { feedData(it) } .subscribe { feedObservable.set(it) shimmerObservable.set(false) retryObservable.set(false) hasValue = true }, { feedContentObservable.set(false) shimmerObservable.set(false) retryObservable.set(!hasValue) } } fun loadBanners() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() .map { bannerData(it) } .subscribe { bannerShimmerObservable.set(false) bannerRetryObservable.set(false) if (!hasValue) { bannerObservable.set(it) } else { (. show some toast) } }, { bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } } fun addSearch() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() .map { bannerData(it) } .subscribe { bannerShimmerObservable.set(false) bannerRetryObservable.set(false) if (!hasValue) { bannerObservable.set(it) } else { (. show some toast) } }, { bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } } } And it goes on Adding Search
  22. class FeedViewModel( private val feedRepository: FeedRepository, private val bannerRepository: BannerRepository

    ) { val feedObservable = ObservableField<FeedData>() val bannerObservable = ObservableField<BannerData>() val bannerShimmerObservable = ObservableField<BannerData>() val bannerRetryObservable = ObservableField<BannerData>() val feedContentObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() var hasValue = feedRepository.hasValue() init { feedView.onRefreshListener.subscribe { ... } feedView.retryListener.subscribe { loadFeed() } feedView.bannerRetryListener.subscribe { loadBanners() } } fun loadFeed() { retryObservable.set(false) feedContentObservable.set(hasValue) shimmerObservable.set(! hasValue) feedRepository .getFeed() .map { feedData(it) } .subscribe { feedObservable.set(it) shimmerObservable.set(false) retryObservable.set(false) hasValue = true }, { feedContentObservable.set(false) shimmerObservable.set(false) retryObservable.set(!hasValue) } } fun loadBanners() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() .map { bannerData(it) } .subscribe { bannerShimmerObservable.set(false) bannerRetryObservable.set(false) if (!hasValue) { bannerObservable.set(it) } else { (. show some toast) } }, { bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } } fun addSearch() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() .map { bannerData(it) } .subscribe { bannerShimmerObservable.set(false) bannerRetryObservable.set(false) if (!hasValue) { bannerObservable.set(it } }, { bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } } fun paginate() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() .map { bannerData(it) } .subscribe { bannerShimmerObservable.set(false) bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } } } And it goes on Adding Search Pagination
  23. class FeedViewModel( private val feedRepository: FeedRepository, private val bannerRepository: BannerRepository

    ) { val feedObservable = ObservableField<FeedData>() val bannerObservable = ObservableField<BannerData>() val bannerShimmerObservable = ObservableField<BannerData>() val bannerRetryObservable = ObservableField<BannerData>() val feedContentObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() var hasValue = feedRepository.hasValue() init { feedView.onRefreshListener.subscribe { ... } feedView.retryListener.subscribe { loadFeed() } feedView.bannerRetryListener.subscribe { loadBanners() } } fun loadFeed() { retryObservable.set(false) feedContentObservable.set(hasValue) shimmerObservable.set(! hasValue) feedRepository .getFeed() .map { feedData(it) } .subscribe { feedObservable.set(it) shimmerObservable.set(false) retryObservable.set(false) hasValue = true }, { feedContentObservable.set(false) shimmerObservable.set(false) retryObservable.set(!hasValue) } } fun loadBanners() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() .map { bannerData(it) } .subscribe { bannerShimmerObservable.set(false) bannerRetryObservable.set(false) if (!hasValue) { bannerObservable.set(it) } else { (. show some toast) } }, { bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } } fun addSearch() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() .map { bannerData(it) } .subscribe { bannerShimmerObservable.set(false) bannerRetryObservable.set(false) if (!hasValue) { bannerObservable.set(it } }, { bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } } fun paginate() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() .map { bannerData(it) } .subscribe { bannerShimmerObservable.set(false) bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } fun collapse() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } } } And it goes on Adding Search Pagination Collapsable Toolbar
  24. class FeedViewModel( private val feedRepository: FeedRepository, private val bannerRepository: BannerRepository

    ) { val feedObservable = ObservableField<FeedData>() val bannerObservable = ObservableField<BannerData>() val bannerShimmerObservable = ObservableField<BannerData>() val bannerRetryObservable = ObservableField<BannerData>() val feedContentObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() val shimmerObservable = ObservableField<Boolean>() val retryObservable = ObservableField<Boolean>() var hasValue = feedRepository.hasValue() init { feedView.onRefreshListener.subscribe { ... } feedView.retryListener.subscribe { loadFeed() } feedView.bannerRetryListener.subscribe { loadBanners() } } fun loadFeed() { retryObservable.set(false) feedContentObservable.set(hasValue) shimmerObservable.set(! hasValue) feedRepository .getFeed() .map { feedData(it) } .subscribe { feedObservable.set(it) shimmerObservable.set(false) retryObservable.set(false) hasValue = true }, { feedContentObservable.set(false) shimmerObservable.set(false) retryObservable.set(!hasValue) } } fun loadBanners() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() .map { bannerData(it) } .subscribe { bannerShimmerObservable.set(false) bannerRetryObservable.set(false) if (!hasValue) { bannerObservable.set(it) } else { (. show some toast) } }, { bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } } fun addSearch() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() .map { bannerData(it) } .subscribe { bannerShimmerObservable.set(false) bannerRetryObservable.set(false) if (!hasValue) { bannerObservable.set(it } }, { bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } } fun paginate() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() .map { bannerData(it) } .subscribe { bannerShimmerObservable.set(false) bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } fun collapse() { bannerShimmerObservable.set(hasValue) bannerRetryObservable.set(false) bannerRepository .getData() bannerShimmerObservable.set(false) if (!hasValue) { bannerRetryObservable.set(true) } else { (. show some toast) } } } } And it goes on Adding Search Pagination Collapsable Toolbar
  25. data class FeedViewModel( private val feedItems: List<FeedItem> ) { val

    feedItemCells = feedItems.map{ FeedItemCell(it) } } BLOCKS ViewModel
  26. class FeedViewBinder( root: ViewGroup ) { private val feedRV =

    root.feeRV fun bindViews(vm: FeedViewModel) { val adapter = FeedAdapter() feedRV.setAdapter(adapter) adapter.setItems(vm.feedItemCells) } } BLOCKS ViewBinder
  27. class FeedController(viewBinder: FeedViewBinder, 
 manager: FeedManager) { fun onCreate() {

    manager.getObservable() .subscribe { emitViewModel(FeedViewModel(it.events)) } } } BLOCKS Controller - Here’s the fun
  28. class FeedController(viewBinder: FeedViewBinder, manager: FeedManager) { private val filterSubject =

    BehaviorSubject.createDefault("") fun onCreate() { Observable.combineLatest( manager.getObservable(), filterSubject.hide(), ::filterItemsBySearch ).subscribe { emitViewModel(FeedViewModel(it.events)) } } fun filter(filterObservable: Observable<String>) { filterObservable.subscribe { filterSubject.onNext(it) } } } BLOCKS Controller - Here’s the fun
  29. class HighlightsControllerHolder(feedController: FeedController, searchController: SearchController) { fun onCreate() { searchController.onCreate()

    with(feedController) { onCreate() onFilter(searchController.onTextChanges()) } } } BLOCKS Controller Holder - Link what’s needed
  30. class FeedActivity(): Activity { @Inject lateinit var highLightsController: HighlightsControllerHolder override

    fun onCreate() { highLightsController.onCreate() } } BLOCKS Activity - just there because it need to exist
  31. • Responsible for a single entity • Centralize all and

    any way of change the entity in a single place • Spread the model changes using reactive flows in a way that every screen that listen to the changes will be notified • Cache the object in a way that, if needed, we can load the last saved version of the entity MANAGERS Concept
  32. abstract class BaseManager<MODEL : Any>() { private abstract val cache:

    CacheManager fun getObservable(): Observable<MODEL> fun getSingle(): Single<MODEL> . . . fun refresh(): Completable { ... } } MANAGERS Base class
  33. abstract class BaseManager<MODEL : Any>() { private abstract val cache:

    CacheManager fun getObservable(): Observable<MODEL> fun getSingle(): Single<MODEL> . . . fun refresh(): Completable { ... } } MANAGERS Base class
  34. abstract class BaseManager<MODEL : Any>() { private abstract val cache:

    CacheManager fun getObservable(): Observable<MODEL> fun getSingle(): Single<MODEL> . . . fun refresh(): Completable { ... } } MANAGERS Base class
  35. abstract class BaseManager<MODEL : Any>() { private abstract val cache:

    CacheManager fun getObservable(): Observable<MODEL> fun getSingle(): Single<MODEL> . . . fun refresh(): Completable { ... } } MANAGERS Base class
  36. MANAGERS Implementation with Manager class FeedController(viewBinder: FeedViewBinder, manager: FeedManager) {

    fun onCreate() { viewBinder.onRefreshListener.subscribe { refresh() } manager.getObservable() .map { FeedViewModel(it) } .subscribe { emitViewModel(it) } } fun refresh() = manager.refresh().subscribe() }
  37. MANAGERS Implementation with Manager class FeedController(viewBinder: FeedViewBinder, manager: FeedManager) {

    fun onCreate() { viewBinder.onRefreshListener.subscribe { refresh() } manager.getObservable() .map { FeedViewModel(it) } .subscribe { emitViewModel(it) } } fun refresh() = manager.refresh().subscribe() }
  38. MANAGERS Implementation with Manager class FeedController(viewBinder: FeedViewBinder, manager: FeedManager) {

    fun onCreate() { viewBinder.onRefreshListener.subscribe { refresh() } manager.getObservable() .map { FeedViewModel(it) } .subscribe { emitViewModel(it) } } fun refresh() = manager.refresh().subscribe() }
  39. MANAGERS The result AccountManager Controller 1 Controller 2 Controller 3

    AccountConnector O bservable Observable O bservable Cached Account Cached Account Cached Account
  40. MANAGERS The result AccountManager Controller 1 Controller 2 Controller 3

    I want more limit AccountConnector O bservable Observable O bservable
  41. MANAGERS The result AccountManager Controller 1 Controller 2 Controller 3

    AccountConnector Request more limit O bservable Observable O bservable
  42. MANAGERS The result AccountManager Controller 1 Controller 2 Controller 3

    AccountConnector New Account O bservable Observable O bservable
  43. MANAGERS The result AccountManager Controller 1 Controller 2 Controller 3

    AccountConnector O bservable Observable O bservable NewAccount New Account New Account