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

Flutter at scale: 2020 Edition

C887ad592770a197f114d0a1d3e3a5a7?s=47 Jorge Coca
January 24, 2020

Flutter at scale: 2020 Edition

Flutter has been out of beta for more than a year. It is an amazing framework UI that lets you run your code pretty much anywhere. There's a lot of expectations around it, incredible apps running at 60fps, but... is it ready to be adopted at scale?

At BMW, we faced that question a while ago, and the answer was YES! We tried it, we loved it, and we collected all the necessary information to prove that Flutter was the right choice for us. In this talk, let me show you our process to make Flutter our preferred tool for developing applications, and how we enable multiple teams around the world to deliver incredible experiences fast and safely.

C887ad592770a197f114d0a1d3e3a5a7?s=128

Jorge Coca

January 24, 2020
Tweet

Transcript

  1. Flutter at scale 1 — Flutter Europe 2020 / Jorge

    Coca @ Very Good Ventures
  2. 2 — Flutter Europe 2020 / Jorge Coca @ Very

    Good Ventures
  3. Flutter at scale 2020 edition 3 — Flutter Europe 2020

    / Jorge Coca @ Very Good Ventures
  4. In 2019... !" 4 — Flutter Europe 2020 / Jorge

    Coca @ Very Good Ventures
  5. In 2020! ! 5 — Flutter Europe 2020 / Jorge

    Coca @ Very Good Ventures
  6. !"# 6 — Flutter Europe 2020 / Jorge Coca @

    Very Good Ventures
  7. !"# !☁ 7 — Flutter Europe 2020 / Jorge Coca

    @ Very Good Ventures
  8. What do you mean by Flutter at scale? 8 —

    Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  9. What do you mean by **** at scale? 9 —

    Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  10. Complex problems, easy solutions 10 — Flutter Europe 2020 /

    Jorge Coca @ Very Good Ventures
  11. What is a problem? 11 — Flutter Europe 2020 /

    Jorge Coca @ Very Good Ventures
  12. What is a problem? → A question raised for inquiry,

    consideration, or solution 11 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  13. What is a problem? → A question raised for inquiry,

    consideration, or solution → A proposition in mathematics or physics stating something to be done 11 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  14. What is a problem? → A question raised for inquiry,

    consideration, or solution → A proposition in mathematics or physics stating something to be done → A source of perplexity, distress, or vexation 11 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  15. In programming, we solve problems with APIs, and positive and

    efficient communication 12 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  16. What problems are we solving in UI development? 13 —

    Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  17. What problems are we solving in UI development? → How

    to simplify and/or improve our UI development? 13 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  18. What problems are we solving in UI development? → How

    to simplify and/or improve our UI development? → How to be more productive? 13 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  19. What problems are we solving in UI development? → How

    to simplify and/or improve our UI development? → How to be more productive? → How can we be more cost efficient? 13 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  20. What problems are we solving in UI development? → How

    to simplify and/or improve our UI development? → How to be more productive? → How can we be more cost efficient? → How can we guarantee that our app is robust? 13 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  21. What problems are we solving in UI development? → How

    to simplify and/or improve our UI development? → How to be more productive? → How can we be more cost efficient? → How can we guarantee that our app is robust? 14 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  22. We already have an answer... 15 — Flutter Europe 2020

    / Jorge Coca @ Very Good Ventures
  23. Dart and Flutter are... 16 — Flutter Europe 2020 /

    Jorge Coca @ Very Good Ventures
  24. Dart and Flutter are... → Simple, UI-oriented -> widgets 16

    — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  25. Dart and Flutter are... → Simple, UI-oriented -> widgets →

    Productive -> hot reload 16 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  26. Dart and Flutter are... → Simple, UI-oriented -> widgets →

    Productive -> hot reload → Cost efficient -> single codebase 16 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  27. Dart and Flutter are... → Simple, UI-oriented -> widgets →

    Productive -> hot reload → Cost efficient -> single codebase → Robustness -> unit, widget, and driver tests 16 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  28. Simple Productive Cost efficient Robust 17 — Flutter Europe 2020

    / Jorge Coca @ Very Good Ventures
  29. Whatever we do that breaks any of those 4 pillars

    will not let us scale ! 18 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  30. Whatever we do that potentiate any of those 4 pillars

    will skyrocket your development! ! 19 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  31. So... what can we do? 20 — Flutter Europe 2020

    / Jorge Coca @ Very Good Ventures
  32. Flutter is a UI toolkit 21 — Flutter Europe 2020

    / Jorge Coca @ Very Good Ventures
  33. UI = f (state) 22 — Flutter Europe 2020 /

    Jorge Coca @ Very Good Ventures
  34. How we manage state has a big impact on our

    app 23 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  35. Try it by yourself... 24 — Flutter Europe 2020 /

    Jorge Coca @ Very Good Ventures
  36. Try it by yourself... → setState 24 — Flutter Europe

    2020 / Jorge Coca @ Very Good Ventures
  37. Try it by yourself... → setState → Redux 24 —

    Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  38. Try it by yourself... → setState → Redux → Mobx

    24 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  39. Try it by yourself... → setState → Redux → Mobx

    → ScopedModel 24 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  40. Try it by yourself... → setState → Redux → Mobx

    → ScopedModel → Provider 24 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  41. Try it by yourself... → setState → Redux → Mobx

    → ScopedModel → Provider → ... others? 24 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  42. ! No one likes developers with their own methods 25

    — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  43. Please use a tool/ system/pattern that is documented, so everyone

    can learn about it 26 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  44. ..but, if you ask me... 27 — Flutter Europe 2020

    / Jorge Coca @ Very Good Ventures
  45. @felangelov - bloclibrary.dev 28 — Flutter Europe 2020 / Jorge

    Coca @ Very Good Ventures
  46. Say hi! 29 — Flutter Europe 2020 / Jorge Coca

    @ Very Good Ventures
  47. Why Bloc? It's simple, predictable, productive, and robust! 30 —

    Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  48. class CounterBloc extends Bloc<CounterEvent, int> { @override int get initialState

    => 0; @override Stream<int> mapEventToState(CounterEvent event) async* { switch (event) { case CounterEvent.decrement: yield state - 1; break; case CounterEvent.increment: yield state + 1; break; } } } 31 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  49. class CounterBloc extends Bloc<CounterEvent, int> { @override int get initialState

    => 0; @override Stream<int> mapEventToState(CounterEvent event) async* { switch (event) { case CounterEvent.decrement: yield state - 1; break; case CounterEvent.increment: yield state + 1; break; } } } 31 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  50. class CounterBloc extends Bloc<CounterEvent, int> { @override int get initialState

    => 0; @override Stream<int> mapEventToState(CounterEvent event) async* { switch (event) { case CounterEvent.decrement: yield state - 1; break; case CounterEvent.increment: yield state + 1; break; } } } 31 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  51. class CounterBloc extends Bloc<CounterEvent, int> { @override int get initialState

    => 0; @override Stream<int> mapEventToState(CounterEvent event) async* { switch (event) { case CounterEvent.decrement: yield state - 1; break; case CounterEvent.increment: yield state + 1; break; } } } 31 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  52. class CounterBloc extends Bloc<CounterEvent, int> { @override int get initialState

    => 0; @override Stream<int> mapEventToState(CounterEvent event) async* { switch (event) { case CounterEvent.decrement: yield state - 1; break; case CounterEvent.increment: yield state + 1; break; } } } 31 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  53. class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) {

    final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context); return Scaffold( appBar: AppBar(title: Text('Counter')), body: BlocBuilder<CounterBloc, int>( builder: (context, count) { return Text('$count'); }, ), floatingActionButton: Column( children: [ FloatingActionButton( child: Icon(Icons.add), onPressed: () { counterBloc.add(CounterEvent.increment); }, ), FloatingActionButton( child: Icon(Icons.remove), onPressed: () { counterBloc.add(CounterEvent.decrement); }, ), ], ), ); } } 32 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  54. class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) {

    final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context); return Scaffold( appBar: AppBar(title: Text('Counter')), body: BlocBuilder<CounterBloc, int>( builder: (context, count) { return Text('$count'); }, ), floatingActionButton: Column( children: [ FloatingActionButton( child: Icon(Icons.add), onPressed: () { counterBloc.add(CounterEvent.increment); }, ), FloatingActionButton( child: Icon(Icons.remove), onPressed: () { counterBloc.add(CounterEvent.decrement); }, ), ], ), ); } } 32 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  55. class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) {

    final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context); return Scaffold( appBar: AppBar(title: Text('Counter')), body: BlocBuilder<CounterBloc, int>( builder: (context, count) { return Text('$count'); }, ), floatingActionButton: Column( children: [ FloatingActionButton( child: Icon(Icons.add), onPressed: () { counterBloc.add(CounterEvent.increment); }, ), FloatingActionButton( child: Icon(Icons.remove), onPressed: () { counterBloc.add(CounterEvent.decrement); }, ), ], ), ); } } 32 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  56. class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) {

    final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context); return Scaffold( appBar: AppBar(title: Text('Counter')), body: BlocBuilder<CounterBloc, int>( builder: (context, count) { return Text('$count'); }, ), floatingActionButton: Column( children: [ FloatingActionButton( child: Icon(Icons.add), onPressed: () { counterBloc.add(CounterEvent.increment); }, ), FloatingActionButton( child: Icon(Icons.remove), onPressed: () { counterBloc.add(CounterEvent.decrement); }, ), ], ), ); } } 32 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  57. class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) {

    final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context); return Scaffold( appBar: AppBar(title: Text('Counter')), body: BlocBuilder<CounterBloc, int>( builder: (context, count) { return Text('$count'); }, ), floatingActionButton: Column( children: [ FloatingActionButton( child: Icon(Icons.add), onPressed: () { counterBloc.add(CounterEvent.increment); }, ), FloatingActionButton( child: Icon(Icons.remove), onPressed: () { counterBloc.add(CounterEvent.decrement); }, ), ], ), ); } } 32 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  58. class WeatherBloc extends Bloc<WeatherEvent, WeatherState> { final WeatherRepository weatherRepository; WeatherBloc({@required

    this.weatherRepository}); @override WeatherState get initialState => WeatherEmpty(); @override Stream<WeatherState> mapEventToState(WeatherEvent event) async* { if (event is FetchWeather) { yield WeatherLoading(); try { final Weather weather = await weatherRepository.getWeather(event.city); yield WeatherLoaded(weather: weather); } catch (_) { yield WeatherError(); } } } } 33 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  59. class WeatherBloc extends Bloc<WeatherEvent, WeatherState> { final WeatherRepository weatherRepository; WeatherBloc({@required

    this.weatherRepository}); @override WeatherState get initialState => WeatherEmpty(); @override Stream<WeatherState> mapEventToState(WeatherEvent event) async* { if (event is FetchWeather) { yield WeatherLoading(); try { final Weather weather = await weatherRepository.getWeather(event.city); yield WeatherLoaded(weather: weather); } catch (_) { yield WeatherError(); } } } } 33 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  60. class WeatherBloc extends Bloc<WeatherEvent, WeatherState> { final WeatherRepository weatherRepository; WeatherBloc({@required

    this.weatherRepository}); @override WeatherState get initialState => WeatherEmpty(); @override Stream<WeatherState> mapEventToState(WeatherEvent event) async* { if (event is FetchWeather) { yield WeatherLoading(); try { final Weather weather = await weatherRepository.getWeather(event.city); yield WeatherLoaded(weather: weather); } catch (_) { yield WeatherError(); } } } } 33 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  61. class WeatherBloc extends Bloc<WeatherEvent, WeatherState> { final WeatherRepository weatherRepository; WeatherBloc({@required

    this.weatherRepository}); @override WeatherState get initialState => WeatherEmpty(); @override Stream<WeatherState> mapEventToState(WeatherEvent event) async* { if (event is FetchWeather) { yield WeatherLoading(); try { final Weather weather = await weatherRepository.getWeather(event.city); yield WeatherLoaded(weather: weather); } catch (_) { yield WeatherError(); } } } } 33 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  62. Productive: IDE generators 34 — Flutter Europe 2020 / Jorge

    Coca @ Very Good Ventures
  63. ... and it is also robust! blocTest( 'emits [0, 1]

    when CounterEvent.increment is added', build: () => counterBloc, act: (bloc) => bloc.add(CounterEvent.increment), expect: [0, 1], ); blocTest( 'emits [0, -1] when CounterEvent.decrement is added', build: () => counterBloc, act: (bloc) => bloc.add(CounterEvent.decrement), expect: [0, -1], ); 35 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  64. ... and it is also robust! blocTest( 'emits [0, 1]

    when CounterEvent.increment is added', build: () => counterBloc, act: (bloc) => bloc.add(CounterEvent.increment), expect: [0, 1], ); blocTest( 'emits [0, -1] when CounterEvent.decrement is added', build: () => counterBloc, act: (bloc) => bloc.add(CounterEvent.decrement), expect: [0, -1], ); 35 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  65. ... and it is also robust! blocTest( 'emits [0, 1]

    when CounterEvent.increment is added', build: () => counterBloc, act: (bloc) => bloc.add(CounterEvent.increment), expect: [0, 1], ); blocTest( 'emits [0, -1] when CounterEvent.decrement is added', build: () => counterBloc, act: (bloc) => bloc.add(CounterEvent.decrement), expect: [0, -1], ); 35 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  66. ... even with async code! blocTest( 'emits [empty, loading, error]

    when fetching weather and network fails', build: () { when(weatherRepository.getWeather(any)).thenThrow(Exception('ooops')); return weatherBloc; }, act: (bloc) => bloc.add(FetchWeather('Warsaw')), expect: [WeatherEmpty(), WeatherLoading(), WeatherError()], ); 36 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  67. ... even with async code! blocTest( 'emits [empty, loading, error]

    when fetching weather and network fails', build: () { when(weatherRepository.getWeather(any)).thenThrow(Exception('ooops')); return weatherBloc; }, act: (bloc) => bloc.add(FetchWeather('Warsaw')), expect: [WeatherEmpty(), WeatherLoading(), WeatherError()], ); 36 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  68. ... even with async code! blocTest( 'emits [empty, loading, error]

    when fetching weather and network fails', build: () { when(weatherRepository.getWeather(any)).thenThrow(Exception('ooops')); return weatherBloc; }, act: (bloc) => bloc.add(FetchWeather('Warsaw')), expect: [WeatherEmpty(), WeatherLoading(), WeatherError()], ); 36 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  69. ... even with async code! blocTest( 'emits [empty, loading, error]

    when fetching weather and network fails', build: () { when(weatherRepository.getWeather(any)).thenThrow(Exception('ooops')); return weatherBloc; }, act: (bloc) => bloc.add(FetchWeather('Warsaw')), expect: [WeatherEmpty(), WeatherLoading(), WeatherError()], ); 36 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  70. With bloc, so far... 37 — Flutter Europe 2020 /

    Jorge Coca @ Very Good Ventures
  71. With bloc, so far... → Simple 37 — Flutter Europe

    2020 / Jorge Coca @ Very Good Ventures
  72. With bloc, so far... → Simple → Productive 37 —

    Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  73. With bloc, so far... → Simple → Productive → Cost

    efficient 37 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  74. With bloc, so far... → Simple → Productive → Cost

    efficient → Robust 37 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  75. Wouldn't be awesome to have an system that is capable

    of telling us what's going on at any given point in time? !"# 38 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  76. Bloc as a BlocDelegate! class SimpleBlocDelegate extends BlocDelegate { @override

    void onEvent(Bloc bloc, Object event) { super.onEvent(bloc, event); print(event); } @override void onTransition(Bloc bloc, Transition transition) { super.onTransition(bloc, transition); print(transition); } @override void onError(Bloc bloc, Object error, StackTrace stacktrace) { super.onError(bloc, error, stacktrace); print('$error, $stacktrace'); } } 39 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  77. Bloc as a BlocDelegate! class SimpleBlocDelegate extends BlocDelegate { @override

    void onEvent(Bloc bloc, Object event) { super.onEvent(bloc, event); print(event); } @override void onTransition(Bloc bloc, Transition transition) { super.onTransition(bloc, transition); print(transition); } @override void onError(Bloc bloc, Object error, StackTrace stacktrace) { super.onError(bloc, error, stacktrace); print('$error, $stacktrace'); } } 39 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  78. Bloc as a BlocDelegate! class SimpleBlocDelegate extends BlocDelegate { @override

    void onEvent(Bloc bloc, Object event) { super.onEvent(bloc, event); print(event); } @override void onTransition(Bloc bloc, Transition transition) { super.onTransition(bloc, transition); print(transition); } @override void onError(Bloc bloc, Object error, StackTrace stacktrace) { super.onError(bloc, error, stacktrace); print('$error, $stacktrace'); } } 39 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  79. Bloc as a BlocDelegate! class SimpleBlocDelegate extends BlocDelegate { @override

    void onEvent(Bloc bloc, Object event) { super.onEvent(bloc, event); print(event); } @override void onTransition(Bloc bloc, Transition transition) { super.onTransition(bloc, transition); print(transition); } @override void onError(Bloc bloc, Object error, StackTrace stacktrace) { super.onError(bloc, error, stacktrace); print('$error, $stacktrace'); } } 39 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  80. All this for free! LoginBloc: { currentState: LoginInitial, event: LoginFormChanged,

    nextState: LoginFormValidationFailure } LoginBloc: { currentState: LoginFormValidationFailure, event: LoginFormChanged, nextState: LoginFormValidationSuccess } LoginBloc: { currentState: LoginFormValidationSuccess, event: LoginSubmitted, nextState: LoginInProgress } LoginBloc: { currentState: LoginInProgress, event: LoginSubmitted, nextState: LoginSuccess } AuthenticationBloc: { currentState: AuthenticationFailure, event: LoggedIn { user: this_is_a_fake_user }, nextState: AuthenticationSuccess } HomeBloc: { currentState: HomeState.storesTab, event: TabSelected { index: 1 }, nextState: HomeState.tasksTab } HomeBloc: { currentState: HomeState.tasksTab, event: TabSelected { index: 2 }, nextState: HomeState.profileTab } 40 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  81. All this for free! LoginBloc: { currentState: LoginInitial, event: LoginFormChanged,

    nextState: LoginFormValidationFailure } LoginBloc: { currentState: LoginFormValidationFailure, event: LoginFormChanged, nextState: LoginFormValidationSuccess } LoginBloc: { currentState: LoginFormValidationSuccess, event: LoginSubmitted, nextState: LoginInProgress } LoginBloc: { currentState: LoginInProgress, event: LoginSubmitted, nextState: LoginSuccess } AuthenticationBloc: { currentState: AuthenticationFailure, event: LoggedIn { user: this_is_a_fake_user }, nextState: AuthenticationSuccess } HomeBloc: { currentState: HomeState.storesTab, event: TabSelected { index: 1 }, nextState: HomeState.tasksTab } HomeBloc: { currentState: HomeState.tasksTab, event: TabSelected { index: 2 }, nextState: HomeState.profileTab } 40 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  82. All this for free! LoginBloc: { currentState: LoginInitial, event: LoginFormChanged,

    nextState: LoginFormValidationFailure } LoginBloc: { currentState: LoginFormValidationFailure, event: LoginFormChanged, nextState: LoginFormValidationSuccess } LoginBloc: { currentState: LoginFormValidationSuccess, event: LoginSubmitted, nextState: LoginInProgress } LoginBloc: { currentState: LoginInProgress, event: LoginSubmitted, nextState: LoginSuccess } AuthenticationBloc: { currentState: AuthenticationFailure, event: LoggedIn { user: this_is_a_fake_user }, nextState: AuthenticationSuccess } HomeBloc: { currentState: HomeState.storesTab, event: TabSelected { index: 1 }, nextState: HomeState.tasksTab } HomeBloc: { currentState: HomeState.tasksTab, event: TabSelected { index: 2 }, nextState: HomeState.profileTab } 40 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  83. All this for free! LoginBloc: { currentState: LoginInitial, event: LoginFormChanged,

    nextState: LoginFormValidationFailure } LoginBloc: { currentState: LoginFormValidationFailure, event: LoginFormChanged, nextState: LoginFormValidationSuccess } LoginBloc: { currentState: LoginFormValidationSuccess, event: LoginSubmitted, nextState: LoginInProgress } LoginBloc: { currentState: LoginInProgress, event: LoginSubmitted, nextState: LoginSuccess } AuthenticationBloc: { currentState: AuthenticationFailure, event: LoggedIn { user: this_is_a_fake_user }, nextState: AuthenticationSuccess } HomeBloc: { currentState: HomeState.storesTab, event: TabSelected { index: 1 }, nextState: HomeState.tasksTab } HomeBloc: { currentState: HomeState.tasksTab, event: TabSelected { index: 2 }, nextState: HomeState.profileTab } 40 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  84. All this for free! LoginBloc: { currentState: LoginInitial, event: LoginFormChanged,

    nextState: LoginFormValidationFailure } LoginBloc: { currentState: LoginFormValidationFailure, event: LoginFormChanged, nextState: LoginFormValidationSuccess } LoginBloc: { currentState: LoginFormValidationSuccess, event: LoginSubmitted, nextState: LoginInProgress } LoginBloc: { currentState: LoginInProgress, event: LoginSubmitted, nextState: LoginSuccess } AuthenticationBloc: { currentState: AuthenticationFailure, event: LoggedIn { user: this_is_a_fake_user }, nextState: AuthenticationSuccess } HomeBloc: { currentState: HomeState.storesTab, event: TabSelected { index: 1 }, nextState: HomeState.tasksTab } HomeBloc: { currentState: HomeState.tasksTab, event: TabSelected { index: 2 }, nextState: HomeState.profileTab } 40 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  85. All this for free! LoginBloc: { currentState: LoginInitial, event: LoginFormChanged,

    nextState: LoginFormValidationFailure } LoginBloc: { currentState: LoginFormValidationFailure, event: LoginFormChanged, nextState: LoginFormValidationSuccess } LoginBloc: { currentState: LoginFormValidationSuccess, event: LoginSubmitted, nextState: LoginInProgress } LoginBloc: { currentState: LoginInProgress, event: LoginSubmitted, nextState: LoginSuccess } AuthenticationBloc: { currentState: AuthenticationFailure, event: LoggedIn { user: this_is_a_fake_user }, nextState: AuthenticationSuccess } HomeBloc: { currentState: HomeState.storesTab, event: TabSelected { index: 1 }, nextState: HomeState.tasksTab } HomeBloc: { currentState: HomeState.tasksTab, event: TabSelected { index: 2 }, nextState: HomeState.profileTab } 40 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  86. All this for free! LoginBloc: { currentState: LoginInitial, event: LoginFormChanged,

    nextState: LoginFormValidationFailure } LoginBloc: { currentState: LoginFormValidationFailure, event: LoginFormChanged, nextState: LoginFormValidationSuccess } LoginBloc: { currentState: LoginFormValidationSuccess, event: LoginSubmitted, nextState: LoginInProgress } LoginBloc: { currentState: LoginInProgress, event: LoginSubmitted, nextState: LoginSuccess } AuthenticationBloc: { currentState: AuthenticationFailure, event: LoggedIn { user: this_is_a_fake_user }, nextState: AuthenticationSuccess } HomeBloc: { currentState: HomeState.storesTab, event: TabSelected { index: 1 }, nextState: HomeState.tasksTab } HomeBloc: { currentState: HomeState.tasksTab, event: TabSelected { index: 2 }, nextState: HomeState.profileTab } 40 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  87. All this for free! LoginBloc: { currentState: LoginInitial, event: LoginFormChanged,

    nextState: LoginFormValidationFailure } LoginBloc: { currentState: LoginFormValidationFailure, event: LoginFormChanged, nextState: LoginFormValidationSuccess } LoginBloc: { currentState: LoginFormValidationSuccess, event: LoginSubmitted, nextState: LoginInProgress } LoginBloc: { currentState: LoginInProgress, event: LoginSubmitted, nextState: LoginSuccess } AuthenticationBloc: { currentState: AuthenticationFailure, event: LoggedIn { user: this_is_a_fake_user }, nextState: AuthenticationSuccess } HomeBloc: { currentState: HomeState.storesTab, event: TabSelected { index: 1 }, nextState: HomeState.tasksTab } HomeBloc: { currentState: HomeState.tasksTab, event: TabSelected { index: 2 }, nextState: HomeState.profileTab } 40 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  88. Jorge, you won't trick me this time! Those are only

    print statements! ! 41 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  89. lumberdash https://github.com/bmw-tech/lumberdash void main() { putLumberdashToWork(withClients: [ ColorizeLumberdash(), FirebaseLumberdash(...), ]);

    logWarning('Hello Warning'); logFatal('Hello Fatal!'); logMessage('Hello Message!'); logError(Exception('Hello Error')); } 42 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  90. lumberdash https://github.com/bmw-tech/lumberdash void main() { putLumberdashToWork(withClients: [ ColorizeLumberdash(), FirebaseLumberdash(...), ]);

    logWarning('Hello Warning'); logFatal('Hello Fatal!'); logMessage('Hello Message!'); logError(Exception('Hello Error')); } 42 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  91. lumberdash https://github.com/bmw-tech/lumberdash void main() { putLumberdashToWork(withClients: [ ColorizeLumberdash(), FirebaseLumberdash(...), ]);

    logWarning('Hello Warning'); logFatal('Hello Fatal!'); logMessage('Hello Message!'); logError(Exception('Hello Error')); } 42 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  92. Bloc is cost efficient import 'package:lumberdash/lumberdash.dart'; class LumberdashBlocDelegate extends BlocDelegate

    { @override void onTransition(Bloc bloc, Transition transition) { super.onTransition(bloc, transition); logMessage(transition); } @override void onError(Bloc bloc, Object error, StackTrace stacktrace) { super.onError(bloc, error, stacktrace); logError(error, stacktrace); } } 43 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  93. With that setup, you get analytics and debug logs without

    development effort 44 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  94. ...not bad, right? ! 45 — Flutter Europe 2020 /

    Jorge Coca @ Very Good Ventures
  95. It looks like this... 46 — Flutter Europe 2020 /

    Jorge Coca @ Very Good Ventures
  96. With bloc, so far... 47 — Flutter Europe 2020 /

    Jorge Coca @ Very Good Ventures
  97. With bloc, so far... → Simple 47 — Flutter Europe

    2020 / Jorge Coca @ Very Good Ventures
  98. With bloc, so far... → Simple → Productive 47 —

    Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  99. With bloc, so far... → Simple → Productive → Cost

    efficient 47 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  100. With bloc, so far... → Simple → Productive → Cost

    efficient → Robust 47 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  101. Do you think we can do more? 48 — Flutter

    Europe 2020 / Jorge Coca @ Very Good Ventures
  102. 49 — Flutter Europe 2020 / Jorge Coca @ Very

    Good Ventures
  103. Do you think we can do more? 50 — Flutter

    Europe 2020 / Jorge Coca @ Very Good Ventures
  104. Do you think we can do more? → Simple 50

    — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  105. Do you think we can do more? → Simple →

    Productive 50 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  106. Do you think we can do more? → Simple →

    Productive → Cost efficient 50 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  107. Do you think we can do more? → Simple →

    Productive → Cost efficient → Robust 50 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  108. We've only seen how to unit test our blocs... 51

    — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  109. How can we truly measure the performance of our app?

    52 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  110. flutter driver ozzie https://github.com/bmw-tech/ozzie.flutter 53 — Flutter Europe 2020 /

    Jorge Coca @ Very Good Ventures
  111. Ozzie With Ozzie, during integration tests, we can capture screenshots

    of every frame we want, and we can also measure the performance of our app, breaking the build if necessary. 54 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  112. flutter driver tests void main() { FlutterDriver driver; setUpAll(() async

    { driver = await FlutterDriver.connect(); }); tearDownAll(() async { if (driver != null) driver.close(); }); test('initial counter is 0', () async { await driver.waitFor(find.text('0')); }); test('initial counter is 0', () async { await driver.tap(find.byType('FloatingActionButton')); await driver.waitFor(find.text('1')); }); } 55 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  113. flutter driver tests void main() { FlutterDriver driver; setUpAll(() async

    { driver = await FlutterDriver.connect(); }); tearDownAll(() async { if (driver != null) driver.close(); }); test('initial counter is 0', () async { await driver.waitFor(find.text('0')); }); test('initial counter is 0', () async { await driver.tap(find.byType('FloatingActionButton')); await driver.waitFor(find.text('1')); }); } 55 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  114. flutter driver tests void main() { FlutterDriver driver; setUpAll(() async

    { driver = await FlutterDriver.connect(); }); tearDownAll(() async { if (driver != null) driver.close(); }); test('initial counter is 0', () async { await driver.waitFor(find.text('0')); }); test('initial counter is 0', () async { await driver.tap(find.byType('FloatingActionButton')); await driver.waitFor(find.text('1')); }); } 55 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  115. flutter driver tests void main() { FlutterDriver driver; setUpAll(() async

    { driver = await FlutterDriver.connect(); }); tearDownAll(() async { if (driver != null) driver.close(); }); test('initial counter is 0', () async { await driver.waitFor(find.text('0')); }); test('initial counter is 0', () async { await driver.tap(find.byType('FloatingActionButton')); await driver.waitFor(find.text('1')); }); } 55 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  116. flutter driver tests void main() { FlutterDriver driver; setUpAll(() async

    { driver = await FlutterDriver.connect(); }); tearDownAll(() async { if (driver != null) driver.close(); }); test('initial counter is 0', () async { await driver.waitFor(find.text('0')); }); test('initial counter is 0', () async { await driver.tap(find.byType('FloatingActionButton')); await driver.waitFor(find.text('1')); }); } 55 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  117. How does it wotk with Ozzie? 56 — Flutter Europe

    2020 / Jorge Coca @ Very Good Ventures
  118. 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'); }); }); test('initial counter is 0', () async { await ozzie.profilePerformance('counter1', () async { await driver.tap(find.byType('FloatingActionButton')); await driver.waitFor(find.text('1')); await ozzie.takeScreenshot('counter_is_1'); }); }); } 57 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  119. 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'); }); }); test('initial counter is 0', () async { await ozzie.profilePerformance('counter1', () async { await driver.tap(find.byType('FloatingActionButton')); await driver.waitFor(find.text('1')); await ozzie.takeScreenshot('counter_is_1'); }); }); } 57 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  120. 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'); }); }); test('initial counter is 0', () async { await ozzie.profilePerformance('counter1', () async { await driver.tap(find.byType('FloatingActionButton')); await driver.waitFor(find.text('1')); await ozzie.takeScreenshot('counter_is_1'); }); }); } 57 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  121. 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'); }); }); test('initial counter is 0', () async { await ozzie.profilePerformance('counter1', () async { await driver.tap(find.byType('FloatingActionButton')); await driver.waitFor(find.text('1')); await ozzie.takeScreenshot('counter_is_1'); }); }); } 57 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  122. 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'); }); }); test('initial counter is 0', () async { await ozzie.profilePerformance('counter1', () async { await driver.tap(find.byType('FloatingActionButton')); await driver.waitFor(find.text('1')); await ozzie.takeScreenshot('counter_is_1'); }); }); } 57 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  123. 58 — Flutter Europe 2020 / Jorge Coca @ Very

    Good Ventures
  124. 59 — Flutter Europe 2020 / Jorge Coca @ Very

    Good Ventures
  125. Recap 60 — Flutter Europe 2020 / Jorge Coca @

    Very Good Ventures
  126. Recap → Complex problems, simple solutions 60 — Flutter Europe

    2020 / Jorge Coca @ Very Good Ventures
  127. Recap → Complex problems, simple solutions → Goals: Simple, productive,

    cost effective, and robust 60 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  128. Recap → Complex problems, simple solutions → Goals: Simple, productive,

    cost effective, and robust → flutter_bloc by @felangelov checks all the boxes 60 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  129. Recap → Complex problems, simple solutions → Goals: Simple, productive,

    cost effective, and robust → flutter_bloc by @felangelov checks all the boxes → lumberdash and ozzie flutter_bloc 60 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  130. Stay in touch! 61 — Flutter Europe 2020 / Jorge

    Coca @ Very Good Ventures
  131. Stay in touch! → @jcocaramos 61 — Flutter Europe 2020

    / Jorge Coca @ Very Good Ventures
  132. Stay in touch! → @jcocaramos → Chicago Flutter Meetup 61

    — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  133. Stay in touch! → @jcocaramos → Chicago Flutter Meetup →

    Very Good Ventures ! 61 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures
  134. Happy coding! !" 62 — Flutter Europe 2020 / Jorge

    Coca @ Very Good Ventures