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

Hot Off The Grill: RIBs

Hot Off The Grill: RIBs

RIBs is the cross-platform architecture framework behind many mobile apps at Uber. The name RIBs is short of Router, Interactor and Builder. This framework is designed for mobile apps with a large number of engineers and nested states.

1f15bea9f4dc605353bd79b21b2e6a9a?s=128

Brian Attwell

November 08, 2017
Tweet

Transcript

  1. Hot Off The Grill Brian Attwell

  2. Router Interactor Builder

  3. Copyright Tsahi Levent-Levi

  4. Three Android apps Three iOS apps Production

  5. 50% reduction in conditional branches + filter statements No large

    classes in our code base Crash free rate of 99.97% and improving (including natives) Build times Still ship every week How we’ve improved Metrics
  6. 1. How architecture affected us 2. Design of RIBs 3.

    Features & tools Agenda
  7. What came before? History

  8. None
  9. LoggedIn
 Activity Simplified Version of Old Rider App

  10. LoggedIn
 Activity Home
 Controller Confirmation
 Controller Location
 Controller Confirmation Controller

    Location Controller Simplified Version of Old Rider App LoggedIn
 Activity Home Controller
  11. LoggedIn
 Activity Home
 Controller Confirmation
 Controller Location
 Controller Custom View

    Custom View Sub Controller Simplified Version of Old Rider App Confirmation Controller LoggedIn
 Activity Home Controller Location Controller
  12. So what is the problem? Roots

  13. Shared Mutable State

  14. LoggedIn
 Activity Home
 Controller Confirmation
 Controller Location
 Controller LoggedIn
 Activity

    Confirmation Controller Simplified Version of Old Rider App Home Controller Location Controller Custom View Custom View Home Controller
  15. LoggedIn
 Activity Home
 Controller Confirmation
 Controller Location
 Controller Confirmation Controller

    Simplified Version of Old Rider App LoggedIn
 Activity Location Controller Home Controller Custom View Custom View Home Controller
  16. LoggedIn
 Activity Home
 Controller Confirmation
 Controller Location
 Controller LoggedIn
 Activity

    Confirmation Controller … … Simplified Version of Old Rider App Home Controller Location Controller Custom View Custom View Home Controller
  17. LoggedIn
 Activity Home
 Controller Confirmation
 Controller Location
 Controller LoggedIn
 Activity

    Confirmation Controller … … Simplified Version of Old Rider App Home Controller Location Controller Custom View Custom View Home Controller
  18. So why is this bad? Friction

  19. Causes state related bugs Makes code hard to modify So

    why is this bad? Friction
  20. Causes state related bugs Makes code hard to modify So

    why is this bad? Friction
  21. class DriverIsOnTheirWayToaster { var isOnTripAndHasNotBeenNotified:Boolean = false fun onCreate(stateStream: TripStateStream)

    { stateStream .state() .subscribe({ (trip, driver) -> if (trip == TripState.ON_THEIR_WAY) { isOnTripAndHasNotBeenNotified = true showToast(driver!!.name) } else if (trip == TripState.ON_TRIP) { isOnTripAndHasNotBeenNotified = false } }) } } Missing invariants
  22. class DriverIsOnTheirWayToaster { var isOnTripAndHasNotBeenNotified:Boolean = false fun onCreate(stateStream: TripStateStream)

    { stateStream .state() .subscribe({ (trip, driver) -> if (trip == TripState.ON_THEIR_WAY) { isOnTripAndHasNotBeenNotified = true showToast(driver!!.name) } else if (trip == TripState.ON_TRIP) { isOnTripAndHasNotBeenNotified = false } }) } } showToast(driver!!.name) class DriverIsOnTheirWayToaster { fun onCreate(stateStream: TripStateStream) { } fun onCreate showToast(driver .name) Missing invariants
  23. class DriverIsOnTheirWayToaster { fun onCreate(driver: Driver) { } } fun

    onCreate showToast(driver.name) Missing invariants
  24. Causes state related bugs Makes code hard to modify So

    why is this bad? Friction
  25. LoggedIn
 Activity Home
 Controller Confirmation
 Controller Location
 Controller LoggedIn
 Activity

    Home Controller Confirmation Controller Location Controller Home Controller Home Controller Home Controller … … New Controller Simplified Version of Old Rider App
  26. LoggedIn
 Activity Home
 Controller Confirmation
 Controller Location
 Controller LoggedIn
 Activity

    Home Controller Confirmation Controller Location Controller Home Controller Home Controller Home Controller … … New Controller Simplified Version of Old Rider App
  27. LoggedIn
 Activity Home
 Controller Confirmation
 Controller Location
 Controller LoggedIn
 Activity

    Home Controller Confirmation Controller Location Controller Home Controller Home Controller Home Controller … … New Controller Simplified Version of Old Rider App
  28. How we designed RIBs Process

  29. None
  30. Invariants/explicit-contracts/shorter-lived-state Open-closed principle Principles to satisfy Core

  31. Invariants/explicit-contracts/shorter-lived-state Open-closed principle Principles to satisfy Core

  32. LoggedIn 
 RIB Pre Trip
 RIB On Trip
 RIB Home


    RIB Confirmation
 RIB Menu
 RIB Refinement
 RIB Trip Home
 RIB Trip Options
 RIB Location
 RIB Airport
 RIB Favorites
 RIB Favorites
 RIB Shortcuts
 RIB RIB Tree
  33. LoggedIn 
 RIB Pre Trip
 RIB On Trip
 RIB Home


    RIB Confirmation
 RIB Menu
 RIB Refinement
 RIB Trip Home
 RIB Trip Options
 RIB Location
 RIB Airport
 RIB Favorites
 RIB Favorites
 RIB Shortcuts
 RIB RIB Tree
  34. LoggedIn 
 RIB Pre Trip
 RIB On Trip
 RIB Home


    RIB Confirmation
 RIB Menu
 RIB Refinement
 RIB Trip Home
 RIB Trip Options
 RIB Location
 RIB Airport
 RIB Favorites
 RIB Favorites
 RIB Shortcuts
 RIB RIB Tree
  35. LoggedIn 
 RIB Pre Trip
 RIB On Trip
 RIB Home


    RIB Confirmation
 RIB Menu
 RIB Refinement
 RIB Trip Home
 RIB Trip Options
 RIB Location
 RIB Airport
 RIB Favorites
 RIB Favorites
 RIB Shortcuts
 RIB RIB Tree
  36. LoggedIn 
 RIB Pre Trip
 RIB On Trip
 RIB Home


    RIB Confirmation
 RIB Menu
 RIB Refinement
 RIB Trip Home
 RIB Trip Options
 RIB Location
 RIB Airport
 RIB Favorites
 RIB Favorites
 RIB Shortcuts
 RIB RIB Tree
  37. https://github.com/uber/RIBs/tree/master/android/tooling/rib-intellij-plugin

  38. RIB Inside a RIB

  39. Interactor Business Logic Presenter (Optional) Translate between models & views

    Router Attach/detach RIBs View (Optional) Build & update UI Builder Instantiate RIB classes Inside a RIB
  40. class RootRouter { fun attachLoggedIn(authToken: AuthToken) { // Create and

    attach child router. loggedInRouter = loggedInBuilder.build(authToken) attachChild(loggedInRouter); // Extra step! Need to specify view attachment point getView().attachChild(loggedInRouter.getView()); } // … } Attaching child routers
  41. class RootRouter { fun attachLoggedIn(authToken: AuthToken) { // Create and

    attach child router. loggedInRouter = loggedInBuilder.build(authToken) attachChild(loggedInRouter); // Extra step! Need to specify view attachment point getView().attachChild(loggedInRouter.getView()); } // … } loggedInRouter = loggedInBuilder.build(authToken) Attaching child routers
  42. class RootRouter { fun attachLoggedIn(authToken: AuthToken) { // Create and

    attach child router. loggedInRouter = loggedInBuilder.build(authToken) attachChild(loggedInRouter); // Extra step! Need to specify view attachment point getView().attachChild(loggedInRouter.getView()); } // … } attachChild(loggedInRouter); Attaching child routers
  43. class RootRouter { fun attachLoggedIn(authToken: AuthToken) { // Create and

    attach child router. loggedInRouter = loggedInBuilder.build(authToken) attachChild(loggedInRouter); // Extra step! Need to specify view attachment point getView().attachChild(loggedInRouter.getView()); } // … } // Extra step! Need to specify view attachment point getView().attachChild(loggedInRouter.getView()); Attaching child routers
  44. Invariants/explicit-contracts/shorter-lived-state Open-closed principle Principles to satisfy Core

  45. Invariants/explicit-contracts/shorter-lived-state Open-closed principle Principles to satisfy Core

  46. class RootRouter { fun attachLoggedIn(authToken: AuthToken) fun detachLoggedIn() fun attachLoggedOut()

    fun detachLoggedOut() } Root router interface Routers sit between each Interactor and its children. They are only used for attaching/detaching children RIBs.
  47. Root RIB
 Places LocationStream on DI graph and causes Rx

    emissions. LoggedOut RIB
 Receives LocationStream from DI graph. Subscribes to LocationStream. LoggedIn RIB
 Receives LocationStream from DI graph. Subscribes to LocationStream. Communicate data downwards Using Rx
  48. Root RIB
 Implements LoggedOut’s Listener interface LoggedOut RIB
 Declares listener

    interface. Invokes listener.onLogin() to notify parent when done logging in. Communicate data upwards Using listeners
  49. What about the other way around? Bonus

  50. class LoggedInBuilder { interface ParentComponent { // Either explicitly list

    dependencies here, // or just list `Subcomponent.Builder` to get // everything you need. LoggedOutListener loggedOutListener(); } @LoggedInScope @Component(dependencies = ParentComponent.class, …) interface LoggedInComponent { … } }
  51. class LoggedInBuilder { interface ParentComponent { // Either explicitly list

    dependencies here, // or just list `Subcomponent.Builder` to get // everything you need. LoggedOutListener loggedOutListener(); } @LoggedInScope @Component(dependencies = ParentComponent.class, …) interface LoggedInComponent { … } }
  52. class LoggedInBuilder { interface ParentComponent { // Either explicitly list

    dependencies here, // or just list `Subcomponent.Builder` to get // everything you need. LoggedOutListener loggedOutListener(); } @LoggedInScope @Component(dependencies = ParentComponent.class, …) interface LoggedInComponent { … } }
  53. Any tooling or features? Bonus x2

  54. class SomeRouter { fun attachRedeemPointsChild(pointsToRedeem:Int) { screenStack.pushScreen( (this, parentView) ->

    redeemBuilder.build(parentView, pointsToRedeem) ); } // … } Backstack Handling Each RIB chooses how to handle backstack for its children. Lots choose the same way. So we’ve build utilities.
  55. @Override protected Step<Step.NoValue, AddressEntryActionableItem> getSteps( final RootActionableItem rootActionableItem, final ScheduledRidesDeepLink

    scheduledRidesDeepLink) { return rootActionableItem .waitUntilSignedIn() .onStep((noValue, rootSignedInActionableItem) -> rootSignedInActionableItem.goToLoggedIn()) .onStep((value, loggedInActionableItem) -> loggedInActionableItem.goToRide()) .onStep((noValue, rideActionableItem) -> rideActionableItem.isOnTrip()) .onStep((isOnTrip, rideActionableItem) -> { if (isOnTrip) { // Terminate the flow return Step.fromOptional(Single.just(Optional.absent())); } else { return rideActionableItem.waitForRequest(); } }) .onStep((noValue, addressEntryActionableItem) -> addressEntryActionableItem.waitForAddressEntry()) .onStep((noValue, addressEntryActionableItem) -> addressEntryActionableItem.showScheduledRideDatePicker( scheduledRidesDeepLink.getSource())) .onStep((noValue, addressEntryActionableItem) -> addressEntryActionableItem.getAddressEntryComponent()) .onStep((component, addressEntryActionableItem) -> trackTravelDeepLink(component)); } Reactive Deeplinking Deeplink workflows are reactive, type safe and mostly isolated from the rest of the code. “Workflows”
  56. Lifecycle and Memory Tooling Each RIB expresses its lifecycle as

    an observable. Our tooling won’t let you forget to bind your subscriptions to this lifecycle or cleanup routers after you’re done with them. class SomeInteractor { fun attachChild(pointsToRedeem:Int) { myObservable .doStuff() // don’t leak memory! .to(new Autodispose.with(this)) .subscribe(s -> …); } }
  57. Lifecycle and Memory Tooling Each RIB expresses its lifecycle as

    an observable. Our tooling won’t let you forget to bind your subscriptions to this lifecycle or cleanup routers after you’re done with them. class SomeInteractor { fun attachChild(pointsToRedeem:Int) { myObservable .doStuff() // don’t leak memory! .to(new Autodispose.with(this)) .subscribe(s -> …); } }
  58. Test Code Generation @Before public void setup() { initMocks(this); interactor

    = TestConfirmationInteractor.create( new CachedExperiments(fetcher), mapStream, confirmationWorkersFactory, pricingConfirmationWorkersFactory, productSelectionPluginPoint, confirmationMapLayersFactory, listener, deviceClass); InteractorHelper.attach(interactor, presenter, router, null); }
  59. LoggedIn 
 RIB LoggedOut 
 RIB LoggedIn 
 RIB Pre

    Trip 
 RIB On Trip 
 RIB Build times A personal favorite
  60. LoggedIn 
 RIB LoggedOut 
 RIB LoggedIn 
 RIB Build

    Target Build Target Build Target Pre Trip 
 RIB On Trip 
 RIB Build Target Build Target Build times A personal favorite
  61. 12 seconds!! Build times A personal favorite

  62. Brian Attwell Android Platform
 @attwellbrian attwell@uber.com github.com/uber/RIBs

  63. Proprietary and confidential © 2017 Uber Technologies, Inc. All rights

    reserved. No part of this document may be reproduced or utilized in any form or by any means, electronic or mechanical, including photocopying, recording, or by any information storage or retrieval systems, without permission in writing from Uber. This document is intended only for the use of the individual or entity to whom it is addressed and contains information that is privileged, confidential or otherwise exempt from disclosure under applicable law. All recipients of this document are notified that the information contained herein includes proprietary and confidential information of Uber, and recipient may not make use of, disseminate, or in any way disclose this document or any of the enclosed information to any person other than employees of addressee to the extent necessary for consultations with authorized personnel of Uber.
  64. Internal Feedback Slide * how I could I improve? *

    was anything hard to follow? * was anything boring? * was anything handwavy bullshit? * was I missing your favorite thing?