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.

C887ad592770a197f114d0a1d3e3a5a7?s=128

Jorge Coca

August 14, 2019
Tweet

Transcript

  1. Our journey to Flutter

  2. None
  3. None
  4. None
  5. None
  6. None
  7. We needed a Digital transformation

  8. We want to be able to regularly release our products

    across all brands, all platforms, and all regions simultaneously, with the same feature capabilities.
  9. None
  10. Some of our challenges

  11. Some of our challenges → Multiple brands (BMW, MINI, BMW

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

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

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

    Motorrad...) → Highly regulated markets → Teams distributed around the world → Security requirements and audits
  15. 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
  16. Learn from your past

  17. What can we do better?

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

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

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

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

    → Never say no to designers → Be full stack and open → Better understanding of business goals
  22. 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
  23. 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
  24. Set goals for the future

  25. Our Engineering Vision Create a platform that is developer friendly,

    developer scalable, and performant, which provides safe experimentation and continuos deployment.
  26. How was the developer experience in 2018?

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

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

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

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

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

    → Android released after iOS → Platform silos, different experiences → Non-friendly APIs → Frustrating
  32. The Core Team

  33. None
  34. Tech explored (March 2018) → Xamarin → React Native →

    Uber RIBs → Flutter
  35. Our Winner

  36. None
  37. What happened to Flutter? (March 2018)

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

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

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

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

    → What is Flutter? And Dart? → No Google Maps? → Who uses it?
  42. What did we like about RIBs?

  43. None
  44. None
  45. Uber RIBs

  46. Uber RIBs → Shared architecture between iOS and Android

  47. Uber RIBs → Shared architecture between iOS and Android →

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

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

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

    Visual architecture → Aggressive composition → Develop features on isolation → IDE tools to generate boilerplate
  51. 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
  52. pssst... I even open sourced this repo: https://github.com/jorgecoca/uber-ribs-awesome-resources !

  53. ...but after some time

  54. ...but after some time → Felt like writing the same

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

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

    code twice → UI language not tied to a platform → Organization challenges, duplicated efforts
  57. None
  58. ...but then, we tried Flutter again

  59. Back in March 2018, we did not understand the full

    potential of Flutter
  60. What happened to Flutter? (August 2018)

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

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

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

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

    → What is Flutter? And Dart? → No Google Maps? → Who uses it?
  65. What is Flutter?

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

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

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

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

    → Render at 60fps (and you can measure it) → Everything is a widget → Aggressive composition
  70. 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
  71. 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
  72. Flutter is like Picasso: you can ask it to paint

    anything, anywhere, and it will deliver outstanding results
  73. None
  74. What is Dart?

  75. What is Dart? → Client-optimized language for fast apps on

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

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

    any platform → Feels natural and familiar → Easy to learn, but missing some features...
  78. 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)
  79. 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
  80. Why Dart?

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

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

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

    Compile to ARM and x64 machine code → Ahead of time (AOT) compilation
  84. 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
  85. 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...)
  86. Dart Sample Future<void> fetchTemperature() async { final response = await

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

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

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

    http.get('weather.com'); if (response.statusCode == 200) { print('Temperature: ${response.body}'); return; } else { throw Exception('Failed fetching temperature'); } }
  90. No Maps? (August 2018)

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

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

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

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

    → Packages openly developed → We could wait → You can do it with Platform Channels
  95. Community and support

  96. Community and support → Vibrant and welcoming community

  97. Community and support → Vibrant and welcoming community → Started

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

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

    Chicago Flutter → Incredible documentation and tutorials → More and more apps developed with Flutter
  100. ...and many other benefits

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

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

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

    → Multiple state management solutions → 3 levels of testing without 3rd party dependencies
  104. Flutter inspired us to transform our development practices, from top

    to bottom
  105. Published many articles in https://medium.com/flutter-community

  106. None
  107. How do we use Flutter?

  108. How do we use Flutter?

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

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

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

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

    → Overall architecture → State management → Testing and automation
  113. Integration with our backend

  114. Integration with our backend → By having full stack teams,

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

    we can have more control over client APIs → Limit business logic and processing on client
  116. 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
  117. 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
  118. Overall architecture

  119. State management, attempt #1

  120. State management, attempt #1

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

  122. State management, attempt #2: flutter_bloc

  123. None
  124. flutter_bloc & equatable

  125. flutter_bloc & equatable → Inspired by MVI and BLoC

  126. flutter_bloc & equatable → Inspired by MVI and BLoC →

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

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

    Business logic out of UI → Predictable code → Easy to test: each action has a reaction
  129. 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
  130. Counter with flutter_bloc

  131. class CounterBloc extends Bloc<CounterEvent, int> { @override int get initialState

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

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

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

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

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

    => 0; @override Stream<int> mapEventToState(CounterEvent event) async* { switch (event) { case CounterEvent.decrement: yield currentState - 1; break; case CounterEvent.increment: yield currentState + 1; break; } } }
  137. class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) {

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

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

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

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

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

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

    final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context); return Scaffold( body: BlocBuilder<CounterBloc, int>( builder: (context, count) { return Text('$count'); },), floatingActionButton: Column( children: [ FloatingActionButton( onPressed: () { counterBloc.dispatch(CounterEvent.increment); }, ), FloatingActionButton( onPressed: () { counterBloc.dispatch(CounterEvent.decrement); }, ), ...
  144. lumberdash void main() { putLumberdashToWork(withClient: SimpleClient()); logWarning('Hello Warning'); logFatal('Hello Fatal!');

    logMessage('Hello Message!'); logError(Exception('Hello Error')); }
  145. lumberdash clients

  146. lumberdash clients → SimpleClient

  147. lumberdash clients → SimpleClient → ColorizeClient

  148. lumberdash clients → SimpleClient → ColorizeClient → SentryClient

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

  150. 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); } }
  151. 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.
  152. Testing and automation

  153. Testing and automation → Remove manual intervention (lint and format,

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

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

    analyzer, translations...) → 3 levels of testing: unit, widget, and integration → 100% coverage by default
  156. None
  157. ozzie.flutter

  158. 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'); }); }); }
  159. 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'); }); }); }
  160. 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'); }); }); }
  161. 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'); }); }); }
  162. ... before we finish, a little bit of self promotion...

  163. Dart 2 In Action

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

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

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

    book! → Published by Manning Publications → Soon to be in Early Access Preview (MEAP)
  167. 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
  168. Droidcon NYC 2019

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

  170. Droidcon NYC 2019 → August 26th ad 27th → Flutter

    at scale
  171. Questions, comments, feedback... ?

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

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

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

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

    → jorgecoca.dev ( )
  176. None