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

Our Journey To Flutter

Our Journey To Flutter

Almost every mobile developer has heard about Flutter and all its benefits: hot reload, a composable UI, run your code almost anywhere… but what motivates a company to start using Flutter? And how do you determine success?

In this talk, I’d like to guide you through our journey at BMW to make Flutter our preferred UI framework, how we enable Flutter development at scale, the challenges that we have faced and our future plans using this amazing technology.

Jorge Coca

August 14, 2019
Tweet

More Decks by Jorge Coca

Other Decks in Programming

Transcript

  1. Our journey
    to Flutter

    View full-size slide

  2. We needed a
    Digital transformation

    View full-size slide

  3. We want to be able to regularly release our
    products across all brands, all platforms,
    and all regions simultaneously, with the
    same feature capabilities.

    View full-size slide

  4. Some of our challenges

    View full-size slide

  5. Some of our challenges
    → Multiple brands (BMW, MINI, BMW Motorrad...)

    View full-size slide

  6. Some of our challenges
    → Multiple brands (BMW, MINI, BMW Motorrad...)
    → Highly regulated markets

    View full-size slide

  7. Some of our challenges
    → Multiple brands (BMW, MINI, BMW Motorrad...)
    → Highly regulated markets
    → Teams distributed around the world

    View full-size slide

  8. Some of our challenges
    → Multiple brands (BMW, MINI, BMW Motorrad...)
    → Highly regulated markets
    → Teams distributed around the world
    → Security requirements and audits

    View full-size slide

  9. Some of our challenges
    → Multiple brands (BMW, MINI, BMW Motorrad...)
    → Highly regulated markets
    → Teams distributed around the world
    → Security requirements and audits
    → Fragmentation of devices and vehicles

    View full-size slide

  10. Learn from
    your past

    View full-size slide

  11. What can we do better?

    View full-size slide

  12. What can we do better?
    → Robust and predictable APIs

    View full-size slide

  13. What can we do better?
    → Robust and predictable APIs
    → Never say no to designers

    View full-size slide

  14. What can we do better?
    → Robust and predictable APIs
    → Never say no to designers
    → Be full stack and open

    View full-size slide

  15. What can we do better?
    → Robust and predictable APIs
    → Never say no to designers
    → Be full stack and open
    → Better understanding of business goals

    View full-size slide

  16. What can we do better?
    → Robust and predictable APIs
    → Never say no to designers
    → Be full stack and open
    → Better understanding of business goals
    → Shorter feature cycle times

    View full-size slide

  17. What can we do better?
    → Robust and predictable APIs
    → Never say no to designers
    → Be full stack and open
    → Better understanding of business goals
    → Shorter feature cycle times
    → Improve our developer experience

    View full-size slide

  18. Set goals for
    the future

    View full-size slide

  19. Our Engineering Vision
    Create a platform that is developer
    friendly, developer scalable, and
    performant, which provides safe
    experimentation and continuos
    deployment.

    View full-size slide

  20. How was the developer experience in
    2018?

    View full-size slide

  21. How was the developer experience in
    2018?
    → Divergent architectures

    View full-size slide

  22. How was the developer experience in
    2018?
    → Divergent architectures
    → Android released after iOS

    View full-size slide

  23. How was the developer experience in
    2018?
    → Divergent architectures
    → Android released after iOS
    → Platform silos, different experiences

    View full-size slide

  24. How was the developer experience in
    2018?
    → Divergent architectures
    → Android released after iOS
    → Platform silos, different experiences
    → Non-friendly APIs

    View full-size slide

  25. How was the developer experience in
    2018?
    → Divergent architectures
    → Android released after iOS
    → Platform silos, different experiences
    → Non-friendly APIs
    → Frustrating

    View full-size slide

  26. The Core Team

    View full-size slide

  27. Tech explored (March 2018)
    → Xamarin
    → React Native
    → Uber RIBs
    → Flutter

    View full-size slide

  28. What happened to Flutter? (March 2018)

    View full-size slide

  29. What happened to Flutter? (March 2018)
    → Another cross-platform framework?

    View full-size slide

  30. What happened to Flutter? (March 2018)
    → Another cross-platform framework?
    → What is Flutter? And Dart?

    View full-size slide

  31. What happened to Flutter? (March 2018)
    → Another cross-platform framework?
    → What is Flutter? And Dart?
    → No Google Maps?

    View full-size slide

  32. What happened to Flutter? (March 2018)
    → Another cross-platform framework?
    → What is Flutter? And Dart?
    → No Google Maps?
    → Who uses it?

    View full-size slide

  33. What did we
    like about
    RIBs?

    View full-size slide

  34. Uber RIBs
    → Shared architecture between iOS and Android

    View full-size slide

  35. Uber RIBs
    → Shared architecture between iOS and Android
    → Visual architecture

    View full-size slide

  36. Uber RIBs
    → Shared architecture between iOS and Android
    → Visual architecture
    → Aggressive composition

    View full-size slide

  37. Uber RIBs
    → Shared architecture between iOS and Android
    → Visual architecture
    → Aggressive composition
    → Develop features on isolation

    View full-size slide

  38. Uber RIBs
    → Shared architecture between iOS and Android
    → Visual architecture
    → Aggressive composition
    → Develop features on isolation
    → IDE tools to generate boilerplate

    View full-size slide

  39. Uber RIBs
    → Shared architecture between iOS and Android
    → Visual architecture
    → Aggressive composition
    → Develop features on isolation
    → IDE tools to generate boilerplate
    → Develop with Kotlin and Swift

    View full-size slide

  40. pssst...
    I even open sourced this repo:
    https://github.com/jorgecoca/uber-ribs-awesome-resources
    !

    View full-size slide

  41. ...but after some time

    View full-size slide

  42. ...but after some time
    → Felt like writing the same code twice

    View full-size slide

  43. ...but after some time
    → Felt like writing the same code twice
    → UI language not tied to a platform

    View full-size slide

  44. ...but after some time
    → Felt like writing the same code twice
    → UI language not tied to a platform
    → Organization challenges, duplicated efforts

    View full-size slide

  45. ...but then, we
    tried Flutter
    again

    View full-size slide

  46. Back in March 2018,
    we did not understand the
    full potential of Flutter

    View full-size slide

  47. What happened to Flutter? (August 2018)

    View full-size slide

  48. What happened to Flutter? (August 2018)
    → Another cross-platform framework?

    View full-size slide

  49. What happened to Flutter? (August 2018)
    → Another cross-platform framework?
    → What is Flutter? And Dart?

    View full-size slide

  50. What happened to Flutter? (August 2018)
    → Another cross-platform framework?
    → What is Flutter? And Dart?
    → No Google Maps?

    View full-size slide

  51. What happened to Flutter? (August 2018)
    → Another cross-platform framework?
    → What is Flutter? And Dart?
    → No Google Maps?
    → Who uses it?

    View full-size slide

  52. What is Flutter?

    View full-size slide

  53. What is Flutter?
    → UI portable toolkit (iOS, Android, web...)

    View full-size slide

  54. What is Flutter?
    → UI portable toolkit (iOS, Android, web...)
    → Render at 60fps (and you can measure it)

    View full-size slide

  55. What is Flutter?
    → UI portable toolkit (iOS, Android, web...)
    → Render at 60fps (and you can measure it)
    → Everything is a widget

    View full-size slide

  56. What is Flutter?
    → UI portable toolkit (iOS, Android, web...)
    → Render at 60fps (and you can measure it)
    → Everything is a widget
    → Aggressive composition

    View full-size slide

  57. What is Flutter?
    → UI portable toolkit (iOS, Android, web...)
    → Render at 60fps (and you can measure it)
    → Everything is a widget
    → Aggressive composition
    → Incredible tooling out of the box

    View full-size slide

  58. What is Flutter?
    → UI portable toolkit (iOS, Android, web...)
    → Render at 60fps (and you can measure it)
    → Everything is a widget
    → Aggressive composition
    → Incredible tooling out of the box
    → Hot reload for faster development

    View full-size slide

  59. Flutter is like Picasso:
    you can ask it to paint anything,
    anywhere, and it will deliver outstanding results

    View full-size slide

  60. What is Dart?

    View full-size slide

  61. What is Dart?
    → Client-optimized language for fast apps on any
    platform

    View full-size slide

  62. What is Dart?
    → Client-optimized language for fast apps on any
    platform
    → Feels natural and familiar

    View full-size slide

  63. What is Dart?
    → Client-optimized language for fast apps on any
    platform
    → Feels natural and familiar
    → Easy to learn, but missing some features...

    View full-size slide

  64. What is Dart?
    → Client-optimized language for fast apps on any
    platform
    → Feels natural and familiar
    → Easy to learn, but missing some features...
    → Future API (async-await)

    View full-size slide

  65. What is Dart?
    → Client-optimized language for fast apps on any
    platform
    → Feels natural and familiar
    → Easy to learn, but missing some features...
    → Future API (async-await)
    → Isolate-based concurrency

    View full-size slide

  66. Why Dart?
    → Hot reload: see changes on real-time

    View full-size slide

  67. Why Dart?
    → Hot reload: see changes on real-time
    → Compile to ARM and x64 machine code

    View full-size slide

  68. Why Dart?
    → Hot reload: see changes on real-time
    → Compile to ARM and x64 machine code
    → Ahead of time (AOT) compilation

    View full-size slide

  69. Why Dart?
    → Hot reload: see changes on real-time
    → Compile to ARM and x64 machine code
    → Ahead of time (AOT) compilation
    → Just in time (JIT) compilation

    View full-size slide

  70. Why Dart?
    → Hot reload: see changes on real-time
    → Compile to ARM and x64 machine code
    → Ahead of time (AOT) compilation
    → Just in time (JIT) compilation
    → Modern tools (profile, debug, lint, observatory...)

    View full-size slide

  71. Dart Sample
    Future fetchTemperature() async {
    final response = await http.get('weather.com');
    if (response.statusCode == 200) {
    print('Temperature: ${response.body}');
    return;
    } else {
    throw Exception('Failed fetching temperature');
    }
    }

    View full-size slide

  72. Dart Sample
    Future fetchTemperature() async {
    final response = await http.get('weather.com');
    if (response.statusCode == 200) {
    print('Temperature: ${response.body}');
    return;
    } else {
    throw Exception('Failed fetching temperature');
    }
    }

    View full-size slide

  73. Dart Sample
    Future fetchTemperature() async {
    final response = await http.get('weather.com');
    if (response.statusCode == 200) {
    print('Temperature: ${response.body}');
    return;
    } else {
    throw Exception('Failed fetching temperature');
    }
    }

    View full-size slide

  74. Dart Sample
    Future fetchTemperature() async {
    final response = await http.get('weather.com');
    if (response.statusCode == 200) {
    print('Temperature: ${response.body}');
    return;
    } else {
    throw Exception('Failed fetching temperature');
    }
    }

    View full-size slide

  75. No Maps? (August 2018)

    View full-size slide

  76. No Maps? (August 2018)
    → Talked to Google about it

    View full-size slide

  77. No Maps? (August 2018)
    → Talked to Google about it
    → Packages openly developed

    View full-size slide

  78. No Maps? (August 2018)
    → Talked to Google about it
    → Packages openly developed
    → We could wait

    View full-size slide

  79. No Maps? (August 2018)
    → Talked to Google about it
    → Packages openly developed
    → We could wait
    → You can do it with Platform Channels

    View full-size slide

  80. Community and support

    View full-size slide

  81. Community and support
    → Vibrant and welcoming community

    View full-size slide

  82. Community and support
    → Vibrant and welcoming community
    → Started Chicago Flutter

    View full-size slide

  83. Community and support
    → Vibrant and welcoming community
    → Started Chicago Flutter
    → Incredible documentation and tutorials

    View full-size slide

  84. Community and support
    → Vibrant and welcoming community
    → Started Chicago Flutter
    → Incredible documentation and tutorials
    → More and more apps developed with Flutter

    View full-size slide

  85. ...and many other benefits

    View full-size slide

  86. ...and many other benefits
    → UI described as a tree

    View full-size slide

  87. ...and many other benefits
    → UI described as a tree
    → Multiple state management solutions

    View full-size slide

  88. ...and many other benefits
    → UI described as a tree
    → Multiple state management solutions
    → 3 levels of testing without 3rd party dependencies

    View full-size slide

  89. Flutter inspired us
    to transform our development
    practices, from top to bottom

    View full-size slide

  90. Published many articles in
    https://medium.com/flutter-community

    View full-size slide

  91. How do we
    use Flutter?

    View full-size slide

  92. How do we use Flutter?

    View full-size slide

  93. How do we use Flutter?
    → Integration with our backend

    View full-size slide

  94. How do we use Flutter?
    → Integration with our backend
    → Overall architecture

    View full-size slide

  95. How do we use Flutter?
    → Integration with our backend
    → Overall architecture
    → State management

    View full-size slide

  96. How do we use Flutter?
    → Integration with our backend
    → Overall architecture
    → State management
    → Testing and automation

    View full-size slide

  97. Integration with our backend

    View full-size slide

  98. Integration with our backend
    → By having full stack teams, we can have more
    control over client APIs

    View full-size slide

  99. Integration with our backend
    → By having full stack teams, we can have more
    control over client APIs
    → Limit business logic and processing on client

    View full-size slide

  100. Integration with our backend
    → By having full stack teams, we can have more
    control over client APIs
    → Limit business logic and processing on client
    → Backends for frontends

    View full-size slide

  101. Integration with our backend
    → By having full stack teams, we can have more
    control over client APIs
    → Limit business logic and processing on client
    → Backends for frontends
    → Feature toggles in backend

    View full-size slide

  102. Overall architecture

    View full-size slide

  103. State management, attempt #1

    View full-size slide

  104. State management, attempt #1

    View full-size slide

  105. Well, we do
    not use Redux
    anymore...

    View full-size slide

  106. State management, attempt #2:
    flutter_bloc

    View full-size slide

  107. flutter_bloc & equatable

    View full-size slide

  108. flutter_bloc & equatable
    → Inspired by MVI and BLoC

    View full-size slide

  109. flutter_bloc & equatable
    → Inspired by MVI and BLoC
    → Business logic out of UI

    View full-size slide

  110. flutter_bloc & equatable
    → Inspired by MVI and BLoC
    → Business logic out of UI
    → Predictable code

    View full-size slide

  111. flutter_bloc & equatable
    → Inspired by MVI and BLoC
    → Business logic out of UI
    → Predictable code
    → Easy to test: each action has a reaction

    View full-size slide

  112. flutter_bloc & equatable
    → Inspired by MVI and BLoC
    → Business logic out of UI
    → Predictable code
    → Easy to test: each action has a reaction
    → Automation (analytics, logs, errors...) in
    BlocDelegate

    View full-size slide

  113. Counter with flutter_bloc

    View full-size slide

  114. class CounterBloc extends Bloc {
    @override
    int get initialState => 0;
    @override
    Stream mapEventToState(CounterEvent event) async* {
    switch (event) {
    case CounterEvent.decrement:
    yield currentState - 1;
    break;
    case CounterEvent.increment:
    yield currentState + 1;
    break;
    }
    }
    }

    View full-size slide

  115. class CounterBloc extends Bloc {
    @override
    int get initialState => 0;
    @override
    Stream mapEventToState(CounterEvent event) async* {
    switch (event) {
    case CounterEvent.decrement:
    yield currentState - 1;
    break;
    case CounterEvent.increment:
    yield currentState + 1;
    break;
    }
    }
    }

    View full-size slide

  116. class CounterBloc extends Bloc {
    @override
    int get initialState => 0;
    @override
    Stream mapEventToState(CounterEvent event) async* {
    switch (event) {
    case CounterEvent.decrement:
    yield currentState - 1;
    break;
    case CounterEvent.increment:
    yield currentState + 1;
    break;
    }
    }
    }

    View full-size slide

  117. class CounterBloc extends Bloc {
    @override
    int get initialState => 0;
    @override
    Stream mapEventToState(CounterEvent event) async* {
    switch (event) {
    case CounterEvent.decrement:
    yield currentState - 1;
    break;
    case CounterEvent.increment:
    yield currentState + 1;
    break;
    }
    }
    }

    View full-size slide

  118. class CounterBloc extends Bloc {
    @override
    int get initialState => 0;
    @override
    Stream mapEventToState(CounterEvent event) async* {
    switch (event) {
    case CounterEvent.decrement:
    yield currentState - 1;
    break;
    case CounterEvent.increment:
    yield currentState + 1;
    break;
    }
    }
    }

    View full-size slide

  119. class CounterBloc extends Bloc {
    @override
    int get initialState => 0;
    @override
    Stream mapEventToState(CounterEvent event) async* {
    switch (event) {
    case CounterEvent.decrement:
    yield currentState - 1;
    break;
    case CounterEvent.increment:
    yield currentState + 1;
    break;
    }
    }
    }

    View full-size slide

  120. class CounterPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    final CounterBloc counterBloc = BlocProvider.of(context);
    return Scaffold(
    body: BlocBuilder(
    builder: (context, count) {
    return Text('$count');
    },),
    floatingActionButton: Column(
    children: [
    FloatingActionButton(
    onPressed: () {
    counterBloc.dispatch(CounterEvent.increment);
    },
    ),
    FloatingActionButton(
    onPressed: () {
    counterBloc.dispatch(CounterEvent.decrement);
    },
    ),
    ...

    View full-size slide

  121. class CounterPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    final CounterBloc counterBloc = BlocProvider.of(context);
    return Scaffold(
    body: BlocBuilder(
    builder: (context, count) {
    return Text('$count');
    },),
    floatingActionButton: Column(
    children: [
    FloatingActionButton(
    onPressed: () {
    counterBloc.dispatch(CounterEvent.increment);
    },
    ),
    FloatingActionButton(
    onPressed: () {
    counterBloc.dispatch(CounterEvent.decrement);
    },
    ),
    ...

    View full-size slide

  122. class CounterPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    final CounterBloc counterBloc = BlocProvider.of(context);
    return Scaffold(
    body: BlocBuilder(
    builder: (context, count) {
    return Text('$count');
    },),
    floatingActionButton: Column(
    children: [
    FloatingActionButton(
    onPressed: () {
    counterBloc.dispatch(CounterEvent.increment);
    },
    ),
    FloatingActionButton(
    onPressed: () {
    counterBloc.dispatch(CounterEvent.decrement);
    },
    ),
    ...

    View full-size slide

  123. class CounterPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    final CounterBloc counterBloc = BlocProvider.of(context);
    return Scaffold(
    body: BlocBuilder(
    builder: (context, count) {
    return Text('$count');
    },),
    floatingActionButton: Column(
    children: [
    FloatingActionButton(
    onPressed: () {
    counterBloc.dispatch(CounterEvent.increment);
    },
    ),
    FloatingActionButton(
    onPressed: () {
    counterBloc.dispatch(CounterEvent.decrement);
    },
    ),
    ...

    View full-size slide

  124. class CounterPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    final CounterBloc counterBloc = BlocProvider.of(context);
    return Scaffold(
    body: BlocBuilder(
    builder: (context, count) {
    return Text('$count');
    },),
    floatingActionButton: Column(
    children: [
    FloatingActionButton(
    onPressed: () {
    counterBloc.dispatch(CounterEvent.increment);
    },
    ),
    FloatingActionButton(
    onPressed: () {
    counterBloc.dispatch(CounterEvent.decrement);
    },
    ),
    ...

    View full-size slide

  125. class CounterPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    final CounterBloc counterBloc = BlocProvider.of(context);
    return Scaffold(
    body: BlocBuilder(
    builder: (context, count) {
    return Text('$count');
    },),
    floatingActionButton: Column(
    children: [
    FloatingActionButton(
    onPressed: () {
    counterBloc.dispatch(CounterEvent.increment);
    },
    ),
    FloatingActionButton(
    onPressed: () {
    counterBloc.dispatch(CounterEvent.decrement);
    },
    ),
    ...

    View full-size slide

  126. class CounterPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    final CounterBloc counterBloc = BlocProvider.of(context);
    return Scaffold(
    body: BlocBuilder(
    builder: (context, count) {
    return Text('$count');
    },),
    floatingActionButton: Column(
    children: [
    FloatingActionButton(
    onPressed: () {
    counterBloc.dispatch(CounterEvent.increment);
    },
    ),
    FloatingActionButton(
    onPressed: () {
    counterBloc.dispatch(CounterEvent.decrement);
    },
    ),
    ...

    View full-size slide

  127. lumberdash
    void main() {
    putLumberdashToWork(withClient: SimpleClient());
    logWarning('Hello Warning');
    logFatal('Hello Fatal!');
    logMessage('Hello Message!');
    logError(Exception('Hello Error'));
    }

    View full-size slide

  128. lumberdash clients

    View full-size slide

  129. lumberdash clients
    → SimpleClient

    View full-size slide

  130. lumberdash clients
    → SimpleClient
    → ColorizeClient

    View full-size slide

  131. lumberdash clients
    → SimpleClient
    → ColorizeClient
    → SentryClient

    View full-size slide

  132. lumberdash clients
    → SimpleClient
    → ColorizeClient
    → SentryClient
    → FirebaseClient

    View full-size slide

  133. flutter_bloc & lumberdash
    class CounterBlocDelegate extends BlocDelegate {
    final LumberdashClient lumberdashClient;
    CounterBlocDelegate(this.lumberdashClient);
    @override
    void onTransition(Transition transition) {
    super.onTransition(transition);
    lumberdashClient.logMessage(transition.toString());
    }
    void onError(Object error, StackTrace stacktrace) {
    super.onError(error, stacktrace);
    lumberdashClient.logError(error, stacktrace);
    }
    }

    View full-size slide

  134. flutter_bloc & lumberdash
    Without developer effort, we can report logs,
    analytics, and errors to the proper system
    depending on the build flavor, in an automated way.

    View full-size slide

  135. Testing and automation

    View full-size slide

  136. Testing and automation
    → Remove manual intervention (lint and format,
    analyzer, translations...)

    View full-size slide

  137. Testing and automation
    → Remove manual intervention (lint and format,
    analyzer, translations...)
    → 3 levels of testing: unit, widget, and integration

    View full-size slide

  138. Testing and automation
    → Remove manual intervention (lint and format,
    analyzer, translations...)
    → 3 levels of testing: unit, widget, and integration
    → 100% coverage by default

    View full-size slide

  139. ozzie.flutter

    View full-size slide

  140. void main() {
    FlutterDriver driver;
    Ozzie ozzie;
    setUpAll(() async {
    driver = await FlutterDriver.connect();
    ozzie = Ozzie.initWith(driver, groupName: 'counter');
    });
    tearDownAll(() async {
    if (driver != null) driver.close();
    ozzie.generateHtmlReport();
    });
    test('initial counter is 0', () async {
    await ozzie.profilePerformance('counter0', () async {
    await driver.waitFor(find.text('0'));
    await ozzie.takeScreenshot('initial_counter_is_0');
    await driver.tap(find.byType('FloatingActionButton'));
    await driver.waitFor(find.text('1'));
    await ozzie.takeScreenshot('counter_is_1');
    });
    });
    }

    View full-size slide

  141. void main() {
    FlutterDriver driver;
    Ozzie ozzie;
    setUpAll(() async {
    driver = await FlutterDriver.connect();
    ozzie = Ozzie.initWith(driver, groupName: 'counter');
    });
    tearDownAll(() async {
    if (driver != null) driver.close();
    ozzie.generateHtmlReport();
    });
    test('initial counter is 0', () async {
    await ozzie.profilePerformance('counter0', () async {
    await driver.waitFor(find.text('0'));
    await ozzie.takeScreenshot('initial_counter_is_0');
    await driver.tap(find.byType('FloatingActionButton'));
    await driver.waitFor(find.text('1'));
    await ozzie.takeScreenshot('counter_is_1');
    });
    });
    }

    View full-size slide

  142. void main() {
    FlutterDriver driver;
    Ozzie ozzie;
    setUpAll(() async {
    driver = await FlutterDriver.connect();
    ozzie = Ozzie.initWith(driver, groupName: 'counter');
    });
    tearDownAll(() async {
    if (driver != null) driver.close();
    ozzie.generateHtmlReport();
    });
    test('initial counter is 0', () async {
    await ozzie.profilePerformance('counter0', () async {
    await driver.waitFor(find.text('0'));
    await ozzie.takeScreenshot('initial_counter_is_0');
    await driver.tap(find.byType('FloatingActionButton'));
    await driver.waitFor(find.text('1'));
    await ozzie.takeScreenshot('counter_is_1');
    });
    });
    }

    View full-size slide

  143. void main() {
    FlutterDriver driver;
    Ozzie ozzie;
    setUpAll(() async {
    driver = await FlutterDriver.connect();
    ozzie = Ozzie.initWith(driver, groupName: 'counter');
    });
    tearDownAll(() async {
    if (driver != null) driver.close();
    ozzie.generateHtmlReport();
    });
    test('initial counter is 0', () async {
    await ozzie.profilePerformance('counter0', () async {
    await driver.waitFor(find.text('0'));
    await ozzie.takeScreenshot('initial_counter_is_0');
    await driver.tap(find.byType('FloatingActionButton'));
    await driver.waitFor(find.text('1'));
    await ozzie.takeScreenshot('counter_is_1');
    });
    });
    }

    View full-size slide

  144. ... before we finish, a little bit of self promotion...

    View full-size slide

  145. Dart 2 In Action

    View full-size slide

  146. Dart 2 In Action
    → Mom! I am writing a book!

    View full-size slide

  147. Dart 2 In Action
    → Mom! I am writing a book!
    → Published by Manning Publications

    View full-size slide

  148. Dart 2 In Action
    → Mom! I am writing a book!
    → Published by Manning Publications
    → Soon to be in Early Access Preview (MEAP)

    View full-size slide

  149. Dart 2 In Action
    → Mom! I am writing a book!
    → Published by Manning Publications
    → Soon to be in Early Access Preview (MEAP)
    → Flutter in Action already in MEAP

    View full-size slide

  150. Droidcon NYC 2019

    View full-size slide

  151. Droidcon NYC 2019
    → August 26th ad 27th

    View full-size slide

  152. Droidcon NYC 2019
    → August 26th ad 27th
    → Flutter at scale

    View full-size slide

  153. Questions, comments, feedback... ?

    View full-size slide

  154. Questions, comments, feedback... ?
    → @jcocaramos

    View full-size slide

  155. Questions, comments, feedback... ?
    → @jcocaramos
    → @ChicagoFlutter

    View full-size slide

  156. Questions, comments, feedback... ?
    → @jcocaramos
    → @ChicagoFlutter
    → github.com/bmw-tech

    View full-size slide

  157. Questions, comments, feedback... ?
    → @jcocaramos
    → @ChicagoFlutter
    → github.com/bmw-tech
    → jorgecoca.dev ( )

    View full-size slide