Understand the Provider/BLoC Pattern in 5 Minutes

There are many ways to handle state within Flutter. Here's how to get started in five minutes.

State management is something you have to get right. If you don't, you can end up with some substantial scalability issues as your application grows. Regarding this, you may have heard a variety of different terminologies used such as Provider, Bloc, Mobx, Redux, RxDart, Scoped Model, and so many more.

In this article we'll be investigating how to get started with the Bloc pattern and the Provider library within Flutter. To keep things simple, our example will be concerned with the state of a Counter and the ability to increment or decrement a count.

Project setup

Ensure we have the same starting point by creating a new Flutter project within the terminal:

# New project
$ flutter create flutter_bloc && cd flutter_bloc

# Open in VS Code
$ code .

We'll then need to add the provider library to our pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter

  provider:

Understanding Business Logic Component (BLoC)

If this is your first introduction to BLoC, you'll be happy to know that it isn't a magic library, instead it's a design pattern that is used to centralise our business logic and expose Observable streams that can be used to compose UI.

Instead of using abstract terms, let's jump in to an example. Create a file at lib/blocs/counter_bloc.dart

import 'package:flutter/material.dart';

class CounterBloc extends ChangeNotifier {
  int _counter = 0;
  int get counter => _counter;

  set counter(int val) {
    _counter = val;
    notifyListeners();
  }
}

Our CounterBloc exposes the counter getter for the current _count, as well as the ability to set the counter to a particular value. When the _count is set, the notifyListeners method is called, updating any listeners.

We can then create a CounterPage which uses the CounterBloc at lib/pages/counter.dart:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'package:flutter_bloc/blocs/counter_bloc.dart';

class CounterPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final CounterBloc counterBloc = Provider.of<CounterBloc>(context);

    return Scaffold(
      body: new Container(
        child: Center(
            child: Text(
          counterBloc.counter.toString(),
          style: TextStyle(fontSize: 62.0),
        )),
      ),
    );
  }
}

The key thing to look at here is this line:

final CounterBloc counterBloc = Provider.of<CounterBloc>(context);

This allows us to get access to the nearest Provider of CounterBloc type inside of our Widget tree. If any values change, they'll trigger a state rebuild and thus, give us the new counter value.

The thing is, there currently isn't any CounterBloc providers registered inside of our application. We'll have to do that inside of main.dart:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/blocs/counter_bloc.dart';
import 'package:provider/provider.dart';

import 'package:flutter_bloc/pages/counter.dart';

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

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<CounterBloc>.value(
        notifier: CounterBloc(), child: MaterialApp(home: CounterPage()));
  }
}

"What if I wanted to register multiple providers?" I hear you say. Well, that's simple. Add in a MultiProvider widget and add your ChangeNotifierProvider to the list of providers like so:

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MultiProvider(providers: [
      ChangeNotifierProvider<CounterBloc>.value(
        notifier: CounterBloc(),
      )
    ], child: MaterialApp(home: CounterPage()));
  }
}

If we run our app we should get the following:

Flutter: BLoC and Provider

Interactivity

What if we wanted to increment the counter from another widget?

If we made an IncrementButton and DecrementButton at lib/widgets/increment.dart and decrement.dart respectively, we might get something like this:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'package:flutter_bloc/blocs/counter_bloc.dart';

class DecrementButton extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final CounterBloc counterBloc = Provider.of<CounterBloc>(context);

    return new FlatButton.icon(
      icon: Icon(Icons.remove),
      label: Text("Remove"),
      onPressed: () => counterBloc.decrement(),
    );
  }
}

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'package:flutter_bloc/blocs/counter_bloc.dart';

class IncrementButton extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final CounterBloc counterBloc = Provider.of<CounterBloc>(context);

    return new FlatButton.icon(
      icon: Icon(Icons.add),
      label: Text("Add"),
      onPressed: () => counterBloc.increment(),
    );
  }
}

We can then add the increment and decrement function to our CounterBloc:

import 'package:flutter/material.dart';

class CounterBloc extends ChangeNotifier {
  int _counter = 10;
  int get counter => _counter;

  set counter(int val) {
    _counter = val;
    notifyListeners();
  }

  increment() {
    counter = counter++;
  }

  decrement() {
    counter = counter--;
  }
}

Finally, we can add this to our lib/pages/counter.dart page:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'package:flutter_bloc/blocs/counter_bloc.dart';
import 'package:flutter_bloc/widgets/decrement.dart';
import 'package:flutter_bloc/widgets/increment.dart';

class CounterPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final CounterBloc counterBloc = Provider.of<CounterBloc>(context);

    return Scaffold(
      body: new Container(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                counterBloc.counter.toString(),
                style: TextStyle(fontSize: 62.0),
              ),
              IncrementButton(),
              DecrementButton()
            ],
          ),
        ),
      ),
    );
  }
}

Any time we select the increment or decrement button, it automatically updates our CounterPage with the new value, as we're listening to changes in our UI:

Increment and Decrement

You can see the code for this article here: https://github.com/developer-school/flutterblocprovider

----------

Paul Halliday

Paul Halliday is an author with a passion for cross platform mobile development. He's a recognised Ionic Community Leader and has created courses, books, and a vast amount of content based around the Ionic ecosystem.