Slide 1

Slide 1 text

Flutter at scale 1 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Flutter at scale 2020 edition 3 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

In programming, we solve problems with APIs, and positive and efficient communication 12 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

We already have an answer... 15 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Simple Productive Cost efficient Robust 17 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Whatever we do that potentiate any of those 4 pillars will skyrocket your development! ! 19 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 31

Slide 31 text

So... what can we do? 20 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

How we manage state has a big impact on our app 23 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

! No one likes developers with their own methods 25 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 43

Slide 43 text

Please use a tool/ system/pattern that is documented, so everyone can learn about it 26 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 44

Slide 44 text

..but, if you ask me... 27 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Why Bloc? It's simple, predictable, productive, and robust! 30 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 48

Slide 48 text

class CounterBloc extends Bloc { @override int get initialState => 0; @override Stream 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

Slide 49

Slide 49 text

class CounterBloc extends Bloc { @override int get initialState => 0; @override Stream 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

Slide 50

Slide 50 text

class CounterBloc extends Bloc { @override int get initialState => 0; @override Stream 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

Slide 51

Slide 51 text

class CounterBloc extends Bloc { @override int get initialState => 0; @override Stream 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

Slide 52

Slide 52 text

class CounterBloc extends Bloc { @override int get initialState => 0; @override Stream 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

Slide 53

Slide 53 text

class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { final CounterBloc counterBloc = BlocProvider.of(context); return Scaffold( appBar: AppBar(title: Text('Counter')), body: BlocBuilder( 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

Slide 54

Slide 54 text

class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { final CounterBloc counterBloc = BlocProvider.of(context); return Scaffold( appBar: AppBar(title: Text('Counter')), body: BlocBuilder( 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

Slide 55

Slide 55 text

class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { final CounterBloc counterBloc = BlocProvider.of(context); return Scaffold( appBar: AppBar(title: Text('Counter')), body: BlocBuilder( 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

Slide 56

Slide 56 text

class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { final CounterBloc counterBloc = BlocProvider.of(context); return Scaffold( appBar: AppBar(title: Text('Counter')), body: BlocBuilder( 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

Slide 57

Slide 57 text

class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { final CounterBloc counterBloc = BlocProvider.of(context); return Scaffold( appBar: AppBar(title: Text('Counter')), body: BlocBuilder( 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

Slide 58

Slide 58 text

class WeatherBloc extends Bloc { final WeatherRepository weatherRepository; WeatherBloc({@required this.weatherRepository}); @override WeatherState get initialState => WeatherEmpty(); @override Stream 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

Slide 59

Slide 59 text

class WeatherBloc extends Bloc { final WeatherRepository weatherRepository; WeatherBloc({@required this.weatherRepository}); @override WeatherState get initialState => WeatherEmpty(); @override Stream 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

Slide 60

Slide 60 text

class WeatherBloc extends Bloc { final WeatherRepository weatherRepository; WeatherBloc({@required this.weatherRepository}); @override WeatherState get initialState => WeatherEmpty(); @override Stream 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

Slide 61

Slide 61 text

class WeatherBloc extends Bloc { final WeatherRepository weatherRepository; WeatherBloc({@required this.weatherRepository}); @override WeatherState get initialState => WeatherEmpty(); @override Stream 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

Slide 62

Slide 62 text

Productive: IDE generators 34 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 63

Slide 63 text

... 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

Slide 64

Slide 64 text

... 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

Slide 65

Slide 65 text

... 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

Slide 66

Slide 66 text

... 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

Slide 67

Slide 67 text

... 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

Slide 68

Slide 68 text

... 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

Slide 69

Slide 69 text

... 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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

Jorge, you won't trick me this time! Those are only print statements! ! 41 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

With that setup, you get analytics and debug logs without development effort 44 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

Do you think we can do more? 48 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

We've only seen how to unit test our blocs... 51 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

How does it wotk with Ozzie? 56 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

58 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

Recap → Complex problems, simple solutions 60 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

Stay in touch! 61 — Flutter Europe 2020 / Jorge Coca @ Very Good Ventures

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

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