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

Tools of the Trade #DCNYC18

Ty Smith
August 27, 2018

Tools of the Trade #DCNYC18

With all the great resources and libraries available, building a new mobile app is easier than it’s ever been! But when your users come to expect your product to be as reliable as running water at a global scale, you’ll need to go above and beyond what’s currently available.

Join Ty as he walks down memory lane to cover the history of the Uber app development process. He’ll cover how it grew, the pain points discovered and lessons learned, and the tooling developed and open-sourced to scale to millions of users served by hundreds of contributors across many apps.

Topics covered will include: Buck, Uber’s architecture solution, RIBs, Proper Experimentation, Continuous integration, Code Verification and Static Analysis, and the company processes needed to facilitate a large app.

Ty Smith

August 27, 2018
Tweet

More Decks by Ty Smith

Other Decks in Programming

Transcript

  1. Many Repos Depedency hell ~/Uber/rider-android/build.gradle dependencies { implementation "com.uber:partner-funnel:1.2.3" implementation

    "com.uber:single-sign-on:0.7.0" implementation "com.uber:storage:2.1.0" implementation "com.uber:networking:2.3.1" implementation "com.uber:analytics:1.9.0" } ~/Uber/partner-funnel-android/build.gradle dependencies { implementation "com.uber:storage:2.2.0" implementation "com.uber:networking:2.0.0" implementation "com.uber:analytics:1.3.5" } @tsmith
  2. Monorepo ~/Uber/Android |-- .git |-- build.gradle |-- apps | |--

    rider | |-- driver | |-- eats |-- features | |-- partner-funnel | |-- single-sign-on |-- libraries | |-- storage | |-- networking | |-- analytics @tsmith
  3. Monorepo ~/Uber/android/apps/rider/build.gradle dependencies { implementation project(":features:partner-funnel") implementation project(":features:single-sign-on") implementation project(":libraries:storage")

    implementation project(":libraries:networking") implementation project(":libraries:analytics") } ~/Uber/android/features/partner-funnel/build.gradle dependencies { implementation project(":libraries:storage") implementation project(":libraries:networking") implementation project(":libraries:analytics") } @tsmith
  4. OKBuck A gradle plugin that lets developers utilize the Buck

    build system on a gradle project. @tsmith | github.com/uber/okbuck
  5. 99.99% reliability of core flows Enable global rollback of core

    flows to a guaranteed working state @tsmith
  6. MVC MVVM VIPER Fat View Controller Fat View Controller View

    based logic separation Locked view & business tree Locked view & business tree Locked view & business tree @tsmith
  7. Motif @motif.Scope interface MainScope { ChildScope child(); @motif.Objects class Objects

    { @Expose Database database() { return new Database(); } } } @tsmith | github.com/uber/motif
  8. Motif @motif.Scope interface ChildScope { ChildController controller(); @motif.Objects class Objects

    { ChildView view() { return new ChildView(); } ChildController controller(Database database, ChildView view) { return new ChildController(database, view); } } } @tsmith | github.com/uber/motif
  9. RIBs Plugins class FooPluginFactory : PluginFactory<Intent, FooPlugin> { override fun

    pluginSwitch() = FOO_PLUGIN_SWITCH override fun isApplicable(intent : Intent) = intent.hasExtra("bar") override fun createNewPlugin(intent : Intent) = FooPlugin(intent) } @tsmith | github.com/uber/crumb
  10. RIBs Plugins class BarPluginPoint : PluginPoint<Intent, BarPlugin>() { override val

    internalPluginFactories = listOf(FooPluginFactory()) } @tsmith | github.com/uber/crumb
  11. RIBs Plugins val barPluginPoint = BarPluginPoint(abFramework) val BarPlugin : BarPlugin?

    = pluginPoint.getPlugin(intent) barPlugin?.doWork() @tsmith | github.com/uber/crumb
  12. Autodispose Extras! — Kotlin extensions — RX lifecycle extensions —

    Architecture Components adapter — Plugins — Errorprone checker @tsmith | github.com/uber/autodispose
  13. Workflow class SingleSignOnWorkflow : HelixRootWorkflow<Step.NoValue, SingleSignOnDeepLink>() { override fun getSteps(rootActionableItem

    : AppRootActionableItem, deeplink : SingleSignOnDeepLink ) : Step<Step.NoValue, SingleSignOnActionableItem> { return rootActionableItem .onStep(waitUntilSignedIn()) .onStep(GoToMain()) .onStep(showFullScreenRib(singleSignOnRib(deeplink))) } } @tsmith | github.com/uber/RIBs/tree/master/android/libraries/rib-workflow
  14. Workflow override fun waitUntilSignedIn() : Step<Step.NoValue, RootSignedInActionableItem> { return Step.from(

    mutableSession .getAuthStateAsync() .filter { it is AuthState.AuthStateLoggedIn } .take(1) .map { Step.Data.toActionableItem(this) } .singleOrError()) } @tsmith | github.com/uber/RIBs/tree/master/android/libraries/rib-workflow
  15. Artist An artist creates views. Artist is a Gradle plugin

    that codegens a base set of Android Views. @tsmith | github.com/uber/artist
  16. Artist class VisibilityTrait : Trait { override fun generateFor( type:

    Builder, initMethod: MethodSpec.Builder, rClass: ClassName, baseType: String) { listOf("visible", "invisible", "gone") .forEach { type.addMethod(createVisibilityConvenienceMethod(it)) } } private fun createVisibilityConvenienceMethod(type: String): MethodSpec { return MethodSpec.methodBuilder("is${type.capitalize()}") .addModifiers(Modifier.PUBLIC) .returns(TypeName.BOOLEAN) .addStatement("return getVisibility() == \$T.${type.toUpperCase()}", TypeNames.Android.View) .build() } } @tsmith | github.com/uber/artist
  17. Artist class SwitchStencil : ViewStencil( extendedType = "android.support.v7.widget.SwitchCompat", constructorCount =

    3, defaultAttrRes = "switchStyle", addedTraits = VisibilityTrait::class.java) { override fun name() = "FooSwitch" } @tsmith | github.com/uber/artist
  18. Artist public class FooSwitch extends SwitchCompat { // Constructors //

    protected init method - provided in every stencil public boolean isVisible() { return getVisibility() == View.VISIBLE; } public boolean isGone() { return getVisibility() == View.GONE; } public boolean isInvisible() { return getVisibility() == View.INVISIBLE; } } @tsmith | github.com/uber/artist
  19. Stylist A stylist creates cool styles. Stylist is a Gradle

    plugin that codegens a base set of Android XML themes. @tsmith | github.com/uber/stylist
  20. Stylist @AutoService(ThemeStencilProvider::class) class SampleThemeStencilProvider : ThemeStencilProvider { private val textSizes

    = StyleItemGroup( StyleItem("textSizeSmall", "12dp"), StyleItem("textSizeMedium","16dp"), StyleItem("textSizeLarge", "20dp") ) override fun stencils() = linkedSetOf( ThemeStencil("Theme.Sample.Dark", "Theme.AppCompat"), ThemeStencil("Theme.Sample.Light", "Theme.AppCompat.Light") ) override fun globalStyleItemGroups() = linkedSetOf( textSizes ) } @tsmith | github.com/uber/stylist
  21. Stylist <?xml version="1.0" encoding="utf-8" standalone="no"?> <resources> <style name="Theme.Sample.Dark" parent="Theme.AppCompat"> <item

    name="textSizeSmall">12dp</item> <item name="textSizeMedium">16dp</item> <item name="textSizeLarge">20dp</item> </style> <style name="Theme.Sample.Light" parent="Theme.AppCompat.Light"> <item name="textSizeSmall">12dp</item> <item name="textSizeMedium">16dp</item> <item name="textSizeLarge">20dp</item> </style> </resources> @tsmith | github.com/uber/stylist
  22. Lint class SwitchCompatUsageXmlDetector : ResourceXmlDetector() { override val applicableElements =

    listOf("android.support.v7.widget.SwitchCompat") override fun visitElement(context:XmlContext, element : Element) : override { context.report( Issue.create("SwitchCompatUsage", "Don't use base view SwitchCompat", "Use Provided FooSwitch", Category.CORRECTNESS, 6, Severity.ERROR, Implementation(XmlUViewUsageDetector::class.java, Scope.RESOURCE_FILE_SCOPE)), element, context.getLocation(element), LINT_ERROR_MESSAGE) } } @tsmith
  23. "I call it my billion-dollar mistake. It was the invention

    of the null reference" - Sir Charles Antony Richard Hoare @tsmith | github.com/uber/NullAway
  24. Errorprone public class MyActivity extends Activity { @Override public void

    onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidInjection.inject(this); } } @tsmith | errorprone.info
  25. Nullaway static void log(Object x) { System.out.println(x.toString()); } static void

    foo() { log(null); } @tsmith | github.com/uber/NullAway
  26. Nullaway warning: [NullAway] passing @Nullable parameter 'null' where @NonNull is

    required log(null); ^ @tsmith | github.com/uber/NullAway
  27. Nullaway static void log(@Nullable Object x) { if (x !=

    null) { System.out.println(x.toString()); } } @tsmith | github.com/uber/NullAway
  28. RAVE Runtime Annotation Validation Engine Ensure models adhere to the

    set of expectations that are described by their annotations @tsmith | github.com/uber/RAVE
  29. RAVE public final class SampleFactory implements ValidatorFactory { @NonNull @Override

    public BaseValidator generateValidator() { return new SampleFactory_Generated_Validator(); } } @tsmith | github.com/uber/RAVE
  30. RAVE @Validated(factory = SampleFactory.class) public class SimpleMatchModel { private String

    match; private static final String MATCHED = "Matched"; private static final String NOT_MATCHED = "NotMatched"; @StringDef({MATCHED, NOT_MATCHED}) @Retention(RetentionPolicy.SOURCE) @interface StringVals { } @NonNull @StringVals public String getMatch() { return match; } } @tsmith | github.com/uber/RAVE
  31. RAVE public void validateMyModel(SimpleModel myModel) { try { Rave.getInstance().validate(myModel); }

    catch (RaveException e) { // handle rave validation error. } } @tsmith
  32. Resources Architecture rewrite - t.uber.com/ribs-1 Dependency hierarchies - t.uber.com/ribs-2 RIBs

    - t.uber.com/ribs-3 Plugins - t.uber.com/ribs-4 Monorepo - t.uber.com/monorepo-1 @tsmith