davidanaya.io

How to add BLoC pattern in your Flutter application

December 04, 2018 | 9 Minute Read

In this article I explain how to transform an existing basic application in Flutter to use BLoC pattern.

bloc

Why BLoC pattern?

Although the examples I use in this article refer to a Flutter application, BLoC (Business Logic Component) pattern can be easily used in any other reactive framework as a way to separate business logic from the view.

The implementation is more or less up to the developer, but the basic idea is that the BLoC layer sits between the UI layer and the data layer. It provides access to the data using streams as outputs and sinks as inputs.

bloc-pattern

This idea behind this architecture is nothing new, but it’s especially powerful when used cross-platform as it allows for the sharing on the business logic and data layers and focuses on the presentation layer for each platform. One of the most interesting resources online to learn about BLoC pattern is this DartConf talk by Paolo Soares.

Flutter & AngularDart code sharing

The counter application

This is the example application we get out of the box when we create a new project with Flutter.

initial-counter

Basically, we have have a counter which we increase every time we press the floating button. This is the code that we get from Flutter, after removing all comments.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

These are the key elements in the code:

  • _counter variable that stores the value of the counter.
  • function _incrementCounter that increments the value of _counter.
  • floating button that executes the function _incrementCounter.

As we have some data (_counter) that will be changing due to user input (floatingButton) we need our widget to re-render when this happens, and the way to do that is by using a StatefulWidget and the setState() function.

The code here is pretty simple and it works perfectly fine, but what if I wanted to share this _counter value in a different page in my application? This obviously wouldn’t work as the data is local to my widget.

The way to do that would be by moving this data along with the mechanisms to update it somewhere else that can be accessed from any page or widget in the application. BLoC pattern allows us to do exactly that.

The counter BLoC application

First, we need to create a new class which will act as the source of truth for our counter. It will store the value in a private variable, and will provide a way to interact with it by using streams and sinks.

If you are familiarized with reactive programming and Rx, streams are the like Observables and sinks are Subjects, so we use streams to listen to changes and sinks to update the data.

import 'dart:async';

import 'package:rxdart/rxdart.dart';
import 'package:rxdart/subjects.dart';

class CounterBloc {
  int _counter = 0;

  final _counter$ = BehaviorSubject<int>(seedValue: 0);
  final _incrementController = StreamController<void>();

  CounterBloc() {
    _incrementController.stream.listen((void _) => _counter$.add(++_counter));
  }

  Sink<void> get increment => _incrementController.sink;

  Stream<int> get counter$ => _counter$.stream;

  void dispose() {
    _incrementController.close();
    _counter$.close();
  }
}

In our case, you can see that we use 2 getters, counter$ to expose changes to our private variable _counter and increment to increase the value.

Note how we link the input sink with the output stream in the constructor. Every time the increment is executed a new value for counter will be emitted.

Now we need a way to access this CounterBloc from a widget, and for that we can use InheritedWidget. This is a special type of widget that will live at the top of our widget tree and will allows us to propagate information down. Also, if something changes, any other widget that utilizes it will be rebuilt.

import 'package:flutter/material.dart';

import 'package:flutter_counter_bloc/bloc/counter_bloc.dart';

class BlocProvider extends InheritedWidget {
  final CounterBloc bloc;

  BlocProvider({Key key, this.bloc, child}) : super(key: key, child: child);

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => true;

  static CounterBloc of(BuildContext context) =>
      (context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider).bloc;
}

All the heavy lifting is done, and now we can simplify our main widget. We no longer need a StatefulWidget as our state is now in the bloc, which we can use through the BlocProvider.

import 'package:flutter/material.dart';
import 'package:flutter_counter_bloc/bloc/bloc_provider.dart';
import 'package:flutter_counter_bloc/bloc/counter_bloc.dart';

void main() {
  final bloc = CounterBloc();
  runApp(MyApp(bloc));
}

class MyApp extends StatelessWidget {
  final CounterBloc bloc;

  MyApp(this.bloc);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
        bloc: bloc,
        child: MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        ));
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  Widget build(BuildContext context) {
    final bloc = BlocProvider.of(context);
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            StreamBuilder(
                stream: bloc.counter$,
                builder: (context, snapshot) => snapshot.hasData
                    ? Text('${snapshot.data}',
                        style: Theme.of(context).textTheme.display1)
                    : CircularProgressIndicator()),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => bloc.increment.add(null),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

In our main function we create the bloc and inject it in our BlocProvider, which wraps our entire application, so that it stands at the top of our widget tree and will then be accessible by any widget.

The counter bloc can be accessed with BlocProvider.of(context) and then use the streams and sinks provided to interact with the data.

We use the increment sink to generate a new event and counter$ stream with a StreamBuilder to rebuild the counter with every new value emitted.

You may think that this might look more complex than our initial design, and I agree, but this one will shine once we start adding widgets and state, and sharing this state in different widgets. Now there is a single source of truth which is separated from the presentation layer, and as soon as anyone changes the value of the counter, everyone will be notified.

That’s all for now, in the following article I explain how to create different blocs in the same application and how to communicate between them. Thanks for reading!