Getting Started With the BLoC Pattern

See how to use the popular BLoC pattern to build your Flutter app architecture and manage the flow of data through your widgets using Dart streams. By Sardor Islomov.

5 (5) · 3 Reviews

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Creating Your First BLoC

Before creating any BLoCs for screens, you’ll build a base class for all BLoCs in the project. Create a directory named bloc in the lib folder. This will be the home for all your BLoC classes.

Create a file in that directory called bloc.dart and add the following:

abstract class Bloc {
  void dispose();
}

All your BLoC classes will conform to this interface. The interface doesn’t do much except force you to add a dispose method. But there’s one small caveat to keep in mind with streams: You have to close them when you don’t need them anymore or they can cause a memory leak. The dispose method is where the app will check for this.

The first BLoC manages the user’s queries and displays the list of articles.

In the bloc directory, create a file, article_list_bloc.dart, and add the following code:

class ArticleListBloc implements Bloc {
  // 1
  final _client = RWClient();
  // 2
  final _searchQueryController = StreamController<String?>();
  // 3
  Sink<String?> get searchQuery => _searchQueryController.sink;
  // 4
  late Stream<List<Article>?> articlesStream;

  ArticleListBloc() {
    // 5
    articlesStream = _searchQueryController.stream
        .asyncMap((query) => _client.fetchArticles(query));
  }

  // 6
  @override
  void dispose() {
    _searchQueryController.close();
  }
}

Keep in mind that you create separate BLoCs for each screen. ArticleListScreen uses ArticleListBloc.

The code in ArticleListBloc does the following:

  1. This line creates instance of RWClient to communicate with raywenderlich.com based on HTTP protocol.
  2. The code gives a private StreamController declaration. It will manage the input sink for this BLoC. StreamControllers use generics to tell the type system what kind of object the stream will emit.
  3. Sink<String?> is a public sink interface for your input controller _searchQueryController. You’ll use this sink to send events to the BLoC.
  4. articlesStream stream acts as a bridge between ArticleListScreen and ArticleListBloc. Basically, the BLoC will stream a list of articles onto the screen. You’ll see late syntax here. It means you have to initialize the variable in the future before you first use it. The late keyword helps you avoid making these variables as null type.
    Note: Learn more about it and other features of null safety in this sound null safety tutorial.
  5. This code processes the input queries sink and build an output stream with a list of articles. asyncMap listens to search queries and uses the RWClient class from the starter project to fetch articles from the API. It pushes an output event to articlesStream when fetchArticles completes with some result.
  6. Finally, in the cleanup method, you close StreamController. If you don’t do this, the IDE complains that the StreamController is leaking.
Note: Learn more about it and other features of null safety in this sound null safety tutorial.

When importing the base class using Option+Return (Alt+Enter), select the second option: Import library package:article_finder/bloc/bloc.dart.

Import package instead of relative path

Import all required packages using Option+Return(Alt+Enter) to solve all the errors.

If you build and run the app, nothing happens.

Article list empty

That’s because you haven’t integrated ArticleListBloc with the ArticleListScreen widget. Next, you’ll integrate the BLoC with the widget tree.

Injecting BLoCs Into the Widget Tree

Now that you have BLoC set up, you need a way to inject it into Flutter’s widget tree. It’s become a Flutter convention to call these types of widgets providers. A provider is a widget that stores data and, well, “provides” it to all its children.

Normally, this would be a job for InheritedWidget. But you need to dispose of BLoCs. The StatefulWidget provides the same service. The syntax is more complex, but the result is the same.

Create a file named bloc_provider.dart in the bloc directory and add the following:

// 1
class BlocProvider<T extends Bloc> extends StatefulWidget {
  final Widget child;
  final T bloc;

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

  // 2
  static T of<T extends Bloc>(BuildContext context) {
    final BlocProvider<T> provider = context.findAncestorWidgetOfExactType()!;
    return provider.bloc;
  }

  @override
  State createState() => _BlocProviderState();
}

class _BlocProviderState extends State<BlocProvider> {
  // 3
  @override
  Widget build(BuildContext context) => widget.child;

  // 4
  @override
  void dispose() {
    widget.bloc.dispose();
    super.dispose();
  }
}

In the code above:

  1. BlocProvider is a generic class. The generic type T is scoped to be an object that implements the Bloc interface. This means the provider can store only BLoC objects.
  2. The of method allows widgets to retrieve the BlocProvider from a descendant in the widget tree with the current build context. This is a common pattern in Flutter.
  3. The widget’s build method is a passthrough to the widget’s child. This widget won’t render anything.
  4. Finally, the only reason the provider inherits from StatefulWidget is to get access to the dispose method. When this widget is removed from the tree, Flutter calls the dispose method, which in turn closes the stream.

Wiring Up the Article List Screen

Now that you have your BLoC layer completed for finding articles, it’s time to put the layer to use.

First, in main.dart, place an Article List BLoC above the material app to store the app’s state. Put your cursor over the MaterialApp and press Option+Return (Alt+Enter on a PC). The IDE will bring up the Flutter widget menu. Select Wrap with a new widget.

IDE Wrap with widget

Wrap that with a BlocProvider of type ArticleListBloc and create an ArticleListBloc in the bloc property.

...
return BlocProvider(
      bloc: ArticleListBloc(),
      child: MaterialApp(
...

Adding widgets above the material app is a great way to add data that needs to be accessed from multiple screens.

Note: This snippet was inspired by this great post by Didier Boelens https://www.didierboelens.com/2018/08/reactive-programming—streams—bloc/. This widget is also not optimized and could be improved. This article will stick with the naive approach, which in most scenarios is fine. Look at the Flutter BLoC Package to solve performance issues or get more functionality to work with BLoCs

Next, replace the build method in article_list_screen.dart to use the ArticleListBloc.

@override
Widget build(BuildContext context) {
  // 1
  final bloc = BlocProvider.of<ArticleListBloc>(context);
  return Scaffold(
    appBar: AppBar(title: const Text('Articles')),
    body: Column(
      children: [
        Padding(
          padding: const EdgeInsets.all(16),
          child: TextField(
            decoration: const InputDecoration(
              border: OutlineInputBorder(),
              hintText: 'Search ...',
            ),
            // 2
            onChanged: bloc.searchQuery.add,
          ),
        ),
        Expanded(
          // 3
          child:_buildResults(bloc),
        )
      ],
    ),
  );
}

Here’s a breakdown:

  1. First, the app instantiates a new ArticleListBloc at the top of the build method. Here, BlocProvider helps to find the required BLoC from the widget tree.
  2. It updates TextField‘s onChanged to submit the text to ArticleListBloc. bloc.searchQuery.add is a void add(T) function of the Sink class. This kicks off the chain of calling RWClient and then emits the found articles to the stream.
  3. It passes the BLoC to the _buildResults method.

Now, update the _buildResults method to add a stream builder and show the results in a list. You can use the ‘Wrap with StreamBuilder’ command to update the code faster.

Widget _buildResults(ArticleListBloc bloc) {
  // 1
  return StreamBuilder<List<Article>?>(
    stream: bloc.articlesStream,
    builder: (context, snapshot) {
      // 2
      final results = snapshot.data;
      if (results == null) {
        return const Center(child: Text('Loading ...'));
      } else if (results.isEmpty) {
        return const Center(child: Text('No Results'));
      }
      // 3
      return _buildSearchResults(results);
    },
  );
}

Widget _buildSearchResults(List<Article> results) {
  return Container();
}

StreamBuilders are the secret sauce to make the BLoC pattern tasty. These widgets listen for events from the stream. The widgets execute builder closure and update the widget tree when they receive new events. You don’t need to call setState() in this tutorial because of StreamBuilder and the BLoC pattern.

In the code above:

  1. StreamBuilder defines the stream property using ArtliceListBloc to understand where to get the article list.
  2. Initially, the stream has no data, which is normal. If there isn’t any data in your stream, the app displays the Loading… message. If there’s an empty list in your stream, the app displays the No Results message.
  3. It passes the search results into the regular method.

Build and run the app to see new states. When you run the app, you see the Loading … message. When you enter random keywords into the search field, you see a No Results message. Otherwise, there will be a blank screen.

Replace _buildSearchResults(List<Article> results) with the following code:

Widget _buildSearchResults(List<Article> results) {
  return ListView.builder(
    itemCount: results.length,
    itemBuilder: (context, index) {
      final article = results[index];
      return InkWell(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16),
          // 1
          child: ArticleListItem(article: article),
        ),
        // 2
        onTap: () {
          // TODO: Later will be implemented
        },
      );
    },
  );
}

In the code above:

  1. ArticleListItem is an already defined widget that shows details of articles in the list.
  2. The onTap closure redirects the user to an article’s details page.

Build and run. Enter some keywords in the search field. The app should now get article results from RWClient and show them in a list.

Articles search results

Nice! That’s real progress.