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.

Brian Attwell

November 08, 2017
Tweet

Other Decks in Programming

Transcript

  1. 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
  2. LoggedIn
 Activity Home
 Controller Confirmation
 Controller Location
 Controller Confirmation Controller

    Location Controller Simplified Version of Old Rider App LoggedIn
 Activity Home Controller
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. class DriverIsOnTheirWayToaster { fun onCreate(driver: Driver) { } } fun

    onCreate showToast(driver.name) Missing invariants
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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.
  25. 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
  26. 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
  27. 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 { … } }
  28. 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 { … } }
  29. 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 { … } }
  30. 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.
  31. @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”
  32. 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 -> …); } }
  33. 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 -> …); } }
  34. 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); }
  35. LoggedIn 
 RIB LoggedOut 
 RIB LoggedIn 
 RIB Pre

    Trip 
 RIB On Trip 
 RIB Build times A personal favorite
  36. 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
  37. 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.
  38. 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?