Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

RIBs - Fragments which work

87 views

Published on

Slides from DevFest Hamburg 2019 talk

Published in: Software
  • Be the first to comment

  • Be the first to like this

RIBs - Fragments which work

  1. 1. RIBs Fragments which work Dmitry Zaytsev, Android Engineer @ Uber 1
  2. 2. Why Fragments? 2
  3. 3. 3
  4. 4. MainActivity 4
  5. 5. ● Encapsulation ● Lifecycle ● Navigation Why Fragments
  6. 6. Fragments are great! 7
  7. 7. Why not Fragments? 8
  8. 8. ● Encapsulation ● Lifecycle ● Navigation ● Scalability Why not Fragments
  9. 9. 10
  10. 10. Listeners No compile-time safety A dynamic type check needs to be performed at runtime which could (and will) lead to crashes. override fun onAttach(context: Context) { super.onAttach(context) if (context is Listener) { listener = context } else if (parentFragment is Listener) { listener = parentFragment as Listener } else { throw RuntimeException() } }
  11. 11. Back Stack 12 FragmentA FragmentB FragmentC
  12. 12. Back Stack Simple yet hard to use Back stack works well when you don’t need to handle back navigation yourself or use children fragments. However, as soon as you need to do any customization it quickly falls apart. // Attach Root fragment supportFragmentManager.beginTransaction() .apply { add(R.id.fragmentContainer, RootFragment.newInstance(), "Root") addToBackStack(null) } .commit() // Attach Child fragment rootFragment.childFragmentManager.beginTransaction() .apply { add(R.id.rootContainer, ChildFragment.newInstance(), null) addToBackStack(null) } .commit() // Press back
  13. 13. MainActivity MainFragment 14
  14. 14. ● Too many lifecycle states ● No compile-time safety when calling listeners ● Nested fragments is an afterthought ● Passing arguments ● Testability ● Non-restrictive API ● Fragments are an Android thing ● Requires glue code Problems
  15. 15. 16
  16. 16. 500+Mobile Engineers who worked on new Uber app 200+ Android Engineers who made at least 1 commint in Android monorepo 500+iOS Engineers who made at least 1 commit in iOS monorepo Some Numbers 17
  17. 17. Account Info
  18. 18. Account Info Balance Pill
  19. 19. Account Info Balance Pill Map
  20. 20. Account Info Balance Pill Safety Info Map
  21. 21. Account Info Balance Pill Safety Info Status Map
  22. 22. M - V - Something 24
  23. 23. Naive MVP 25 Model Presenter View Update Read Notify Populate
  24. 24. ● Works well for individual pieces ● Countless variations of the pattern ● Requires glue code ● Development slows down ● Integration points are main source of bugs Naive MVP / MVVM
  25. 25. RIBs 27
  26. 26. Router Interactor Builder 28
  27. 27. Router Interactor Builder 29
  28. 28. Router Interactor Builder 30
  29. 29. Naive MVP 31 Model Presenter View Update Read Notify Populate
  30. 30. RIB 32 Interactor Presenter View Notify Present Notify Populate Model Update Read
  31. 31. RIB 33 Interactor Presenter View Notify Present Notify Populate Model Update Read Router Navigate
  32. 32. RIB 34 Interactor Presenter View Notify Present Notify Populate Model Update Read Router Navigate Builder Build
  33. 33. Example 35
  34. 34. ProductList ProductDetails
  35. 35. Interactor Simple Lifecycle Just 2 lifecycle events. Sometimes even less than that. @Override protected void didBecomeActive(@Nullable Bundle savedInstanceState) { super.didBecomeActive(savedInstanceState); // Subscribe to data source. Ready to update UI. } @Override protected void willResignActive(@Nullable Bundle savedInstanceState) { super.didBecomeActive(savedInstanceState); // Tear down and remove subscriptions/callbacks. }
  36. 36. Interactor Business Logic Loads data from Model layer, makes decisions and asks Presenter to render UI. Talks to Router to perform navigation. @Override protected void didBecomeActive(@Nullable Bundle savedInstanceState) { super.didBecomeActive(savedInstanceState); presenter.showLoading(); productApi.getProducts() .observeOn(AndroidSchedulers.mainThread()) .as(autoDisposable(this)) .subscribe(result -> { presenter.hideLoading(); if (result.isSuccess()) { presenter.showProducts(result.getResult()); } else { presenter.showError(); } }); }
  37. 37. Router Navigation Creates other RIBs and attaches them to view hierarchy. void routeToProductDetails(Product product) { ProductDetailsRouter router = productDetailsBuilder.build( getView(), product ); attachChild(router); getView().addView(router.getView()); }
  38. 38. Builder Builds Instantiates Interactor, Router and View. Provides dependencies. Specifies which dependencies to request from a parent RIB. public Router build() { // Builds and returns a router }
  39. 39. Builder Configuration Instantiates Interactor, Router and View. Provides dependencies. Specifies which dependencies to request from a parent RIB. @Override protected ProductListView inflateView(LayoutInflater inflater, ViewGroup parentViewGroup) { return (ProductListView) inflater.inflate(R.layout.root_rib, parentViewGroup, false); } public interface ParentComponent extends ProductDetailsBuilder.ParentComponent { ProductNetworkApi productNetworkApi(); } @dagger.Module public abstract static class Module { @BindsInstance public abstract ProductDetailsInteractor.Listener productDetailsListener( ProductListInteractor interactor ); } @dagger.Component( modules = RootBuilder.Module.class, dependencies = ParentComponent.class ) interface Component extends InteractorBaseComponent<ProductListInteractor>, ProductDetailsBuilder.ParentComponent { // Dagger initialization }
  40. 40. Builder Configuration Instantiates Interactor, Router and View. Provides dependencies. Specifies which dependencies to request from a parent RIB. @Override protected ProductListView inflateView(LayoutInflater inflater, ViewGroup parentViewGroup) { return (ProductListView) inflater.inflate(R.layout.root_rib, parentViewGroup, false); } public interface ParentComponent extends ProductDetailsBuilder.ParentComponent { ProductNetworkApi productNetworkApi(); } @dagger.Module public abstract static class Module { @BindsInstance public abstract ProductDetailsInteractor.Listener productDetailsListener( ProductListInteractor interactor ); } @dagger.Component( modules = RootBuilder.Module.class, dependencies = ParentComponent.class ) interface Component extends InteractorBaseComponent<ProductListInteractor>, ProductDetailsBuilder.ParentComponent { // Dagger initialization }
  41. 41. Builder Configuration Instantiates Interactor, Router and View. Provides dependencies. Specifies which dependencies to request from a parent RIB. @Override protected ProductListView inflateView(LayoutInflater inflater, ViewGroup parentViewGroup) { return (ProductListView) inflater.inflate(R.layout.root_rib, parentViewGroup, false); } public interface ParentComponent extends ProductDetailsBuilder.ParentComponent { ProductNetworkApi productNetworkApi(); } @dagger.Module public abstract static class Module { @BindsInstance public abstract ProductDetailsInteractor.Listener productDetailsListener( ProductListInteractor interactor ); } @dagger.Component( modules = RootBuilder.Module.class, dependencies = ParentComponent.class ) interface Component extends InteractorBaseComponent<ProductListInteractor>, ProductDetailsBuilder.ParentComponent { // Dagger initialization }
  42. 42. Builder Configuration Instantiates Interactor, Router and View. Provides dependencies. Specifies which dependencies to request from a parent RIB. @Override protected ProductListView inflateView(LayoutInflater inflater, ViewGroup parentViewGroup) { return (ProductListView) inflater.inflate(R.layout.root_rib, parentViewGroup, false); } public interface ParentComponent extends ProductDetailsBuilder.ParentComponent { ProductNetworkApi productNetworkApi(); } @dagger.Module public abstract static class Module { @BindsInstance public abstract ProductDetailsInteractor.Listener productDetailsListener( ProductListInteractor interactor ); } @dagger.Component( modules = RootBuilder.Module.class, dependencies = ParentComponent.class ) interface Component extends InteractorBaseComponent<ProductListInteractor>, ProductDetailsBuilder.ParentComponent { // Dagger initialization }
  43. 43. Builder Configuration Instantiates Interactor, Router and View. Provides dependencies. Specifies which dependencies to request from a parent RIB. @Override protected ProductListView inflateView(LayoutInflater inflater, ViewGroup parentViewGroup) { return (ProductListView) inflater.inflate(R.layout.root_rib, parentViewGroup, false); } public interface ParentComponent extends ProductDetailsBuilder.ParentComponent { ProductNetworkApi productNetworkApi(); } @dagger.Module public abstract static class Module { @BindsInstance public abstract ProductDetailsInteractor.Listener productDetailsListener( ProductListInteractor interactor ); } @dagger.Component( modules = RootBuilder.Module.class, dependencies = ParentComponent.class ) interface Component extends InteractorBaseComponent<ProductListInteractor>, ProductDetailsBuilder.ParentComponent { // Dagger initialization }
  44. 44. Composition 46
  45. 45. Account Info Balance Pill Safety Info Status Map
  46. 46. 48 Root
  47. 47. Root Logged InLogged Out
  48. 48. 50 Root Logged InLogged Out Sign Up Log In
  49. 49. 51 Root Logged InLogged Out MapSign Up Log In
  50. 50. 52 Root Logged InLogged Out Map Balance StatusProfile Sign Up Log In
  51. 51. 53 Root Logged InLogged Out Map Balance StatusProfile Payments Sign Up Log In
  52. 52. Where is Activity? 54
  53. 53. 55 Root Logged InLogged Out Map Balance StatusProfile Payments Sign Up Log In RootActivity
  54. 54. Modularization 56
  55. 55. 57 Root Logged InLogged Out Map Balance StatusProfile Payments Sign Up Log In
  56. 56. + 58
  57. 57. What’s the catch? 59
  58. 58. ● Screen rotation ● Transition animations Not included
  59. 59. Where to grab it? 63
  60. 60. uber/RIBs 64
  61. 61. ● Good parts of MVP ● Composable ● Compile-time independent ● OS agnostic ● Scalable ● Single-Activity as a bonus Summary
  62. 62. Thank you! Time for questions Dmitry Zaytsev, dmitry.zaytsev@uber.com Twitter: @dizaytsev Medium: @dmitry.zaicew

×