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

Flutter at scale: 2020 Edition

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.

Jorge Coca

January 24, 2020
Tweet

More Decks by Jorge Coca

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide