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. We want to be able to regularly release our products

    across all brands, all platforms, and all regions simultaneously, with the same feature capabilities.
  2. Some of our challenges → Multiple brands (BMW, MINI, BMW

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

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

    Motorrad...) → Highly regulated markets → Teams distributed around the world → Security requirements and audits
  5. 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
  6. What can we do better? → Robust and predictable APIs

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

    → Never say no to designers → Be full stack and open → Better understanding of business goals
  8. 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
  9. 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
  10. Our Engineering Vision Create a platform that is developer friendly,

    developer scalable, and performant, which provides safe experimentation and continuos deployment.
  11. How was the developer experience in 2018? → Divergent architectures

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

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

    → Android released after iOS → Platform silos, different experiences → Non-friendly APIs → Frustrating
  14. What happened to Flutter? (March 2018) → Another cross-platform framework?

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

    → What is Flutter? And Dart? → No Google Maps? → Who uses it?
  16. Uber RIBs → Shared architecture between iOS and Android →

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

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

    Visual architecture → Aggressive composition → Develop features on isolation → IDE tools to generate boilerplate
  19. 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
  20. ...but after some time → Felt like writing the same

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

    code twice → UI language not tied to a platform → Organization challenges, duplicated efforts
  22. What happened to Flutter? (August 2018) → Another cross-platform framework?

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

    → What is Flutter? And Dart? → No Google Maps? → Who uses it?
  24. What is Flutter? → UI portable toolkit (iOS, Android, web...)

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

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

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

    anything, anywhere, and it will deliver outstanding results
  30. What is Dart? → Client-optimized language for fast apps on

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

    any platform → Feels natural and familiar → Easy to learn, but missing some features...
  32. 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)
  33. 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
  34. Why Dart? → Hot reload: see changes on real-time →

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

    Compile to ARM and x64 machine code → Ahead of time (AOT) compilation
  36. 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
  37. 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...)
  38. 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'); } }
  39. 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'); } }
  40. 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'); } }
  41. 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'); } }
  42. No Maps? (August 2018) → Talked to Google about it

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

    → Packages openly developed → We could wait → You can do it with Platform Channels
  44. Community and support → Vibrant and welcoming community → Started

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

    Chicago Flutter → Incredible documentation and tutorials → More and more apps developed with Flutter
  46. ...and many other benefits → UI described as a tree

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

    → Multiple state management solutions → 3 levels of testing without 3rd party dependencies
  48. How do we use Flutter? → Integration with our backend

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

    → Overall architecture → State management → Testing and automation
  50. Integration with our backend → By having full stack teams,

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

    we can have more control over client APIs → Limit business logic and processing on client
  52. 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
  53. 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
  54. flutter_bloc & equatable → Inspired by MVI and BLoC →

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

    Business logic out of UI → Predictable code → Easy to test: each action has a reaction
  56. 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
  57. 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; } } }
  58. 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; } } }
  59. 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; } } }
  60. 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; } } }
  61. 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; } } }
  62. 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; } } }
  63. 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); }, ), ...
  64. 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); }, ), ...
  65. 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); }, ), ...
  66. 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); }, ), ...
  67. 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); }, ), ...
  68. 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); }, ), ...
  69. 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); }, ), ...
  70. 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); } }
  71. 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.
  72. Testing and automation → Remove manual intervention (lint and format,

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

    analyzer, translations...) → 3 levels of testing: unit, widget, and integration → 100% coverage by default
  74. 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'); }); }); }
  75. 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'); }); }); }
  76. 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'); }); }); }
  77. 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'); }); }); }
  78. Dart 2 In Action → Mom! I am writing a

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

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