Flutter Build Apps using Bloc Pattern

Gianni Deplano
13 min readDec 19, 2020

--

Flutter is a great solution made by Google to implement Apps from a single codebase to be runnable on multiple platforms, mainly iOS and Android Apps. His main feature is to make the development incredibly fast and easy with native-like performances. Differently than other cross-platform solutions born in the market in the last 10 years, flutter seems born to be really “cross”, probably because the technology is finally mature and convergent in terms of abstraction and programming languages.

Common questions when you start with these tools are: “what are the limitations?”, “what about performances?”, “what about layout separation and architecture?”. Today I’ll like to give a quick sample of a new very interesting architectural pattern supported by Flutter named BLoC in a real-world application.

The use-case we are going to implement is a complete News App.
The App has two screens:

  • The homepage allows to list news fetched from an external web service;
  • The detail page shows the original content in a web view.

The interesting thing that I’ll share with you is the total time I spent to develop the complete example that is less than two hours!

Let’s start by explaining the App and the detail of the case implemented.

The HomePage is the entry point of the App and shows a list of news that comes from a web service. IT will be able to show different contents depending on the “state” of the loading. For instance, when is empty, loading, or loaded.

When the application is launched, the contents are empty and an image and a button will be displayed. On tap, the App invokes a Webservice that fetches the contents and displays a scrollable list. On click on each item, the App simply navigates on the detail page.

On this page, the user is able to share the content on social networks, depending on what is currently installed on the device.

So, very easy use case but very replicable in the real life to study and experiment with the BLoC architectural pattern.

Like other programming patterns like MVC, MVVM, MVP, BLoC has the aim to simplify and separate the logical parts of an application. The right separation of layers and the right architecture allows not only to create more complex and maintainable applications but also to be fast in the development and in the evolution of the codebase.

BLoC stands for Business Logic Components. The gist of BLoC is that everything in the app should be represented as a stream of events: widgets submit events; other widgets will respond. BLoC sits in the middle, managing the conversation. Dart even comes with the syntax for working with streams that are baked into the language. So the App is logically a final state machine that declares states and events during the lifecycle and the state change has the consequence to interact with the views (or part of the views) to show something different.

The App is divided into three main layers logically explained in the picture:

The Data Layer defined the networking part and the models, so in this part, we’ll implement the calls to web services instead of database interactions.

The BLoC layer defined the state machine in terms of the declaration of states and events. The communication with the frontend is implemented with a bidirectional stream that allows the communication with the different layers of the architecture.

On top, the frontend layer implements the experience and the widgets to be displayed.

This architectural contract is not too dissimilar from classical MVC. The Frontend layer can only talk to the BLoC layer. The BLoC layer sends events to the data and UI layers and processes business logic. This structure can scale nicely as the app grows.

The dart:async package provides Streams. The stream is like a Future, but instead of returning a single value asynchronously, streams can yield multiple values over time. If a Future is a value that will be provided eventually, a stream of a series of values that will be provided sporadically over time. This is a very important thing. A Future usually ends with a return keyword that ends the flow. When you’ll work with BLoC the return statement have to be replaced with the yield keyword. Think about yield like a return but that cannot end the function.

The dart:async package provides an object called StreamController. StreamControllers are manager objects that instantiate both a stream and a sink. A sink is the opposite of a stream. If a stream yields output values over time, a sink accepts input values over time.

To summarize, BLoCs are objects that process and store business logic, use sinks to accept input, and provide output via streams.

We’ll start with the web services. For this sample, we’ll implement a call to a public and free service exposed by The Guardian. You’ll need to register (free) on https://bonobo.capi.gutools.co.uk/register/developer to get the API-KEY needed to invoke the APIs.

To get the list of contents will invoke the search function that returns a list of contents by topic:

https://content.guardianapis.com/search?api-key={api-key}&q={query}

Full documentation at the following link https://open-platform.theguardian.com/documentation/search

Let’s start with Flutter. First, create an empty project and run it on your device or simulator to be sure that everything is working. Now we’ll start adding some dependencies in the pubspec.yaml useful to extend the framework for some features we are going to implements.

To implement the Use-case we’ll use these external libs found on pub.dev:

  • cupertino_icons:
    Default icons asset for Cupertino widgets based on Apple styled icons
  • equatable:
    An abstract class that helps to implement equality without needing to explicitly override == and hashCode.
  • flutter_bloc:
    Flutter Widgets that make it easy to implement the BLoC (Business Logic Component) design pattern. Built to be used with the bloc state management package.
  • flutter_inappwebview:
    A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
  • http:
    A composable, multi-platform, Future-based API for HTTP requests.
  • intl:
    Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.
  • share:
    To manage the URL sharing using the Apps currently available on the Device

At this stage we’ll start implementing the networking-data layer. We create a data folder inside libs with two subfolders:

  • Models
  • Repository

The model is a dart class that contains all the fields needed to map the response received from the API. The repository is used to implement the web service client and the repository linked to the BLoC layer.

Let’s begin with the model. Looking to the JSON received from the Web service, we’ll map the fields inside:

Look to Equatable is used in the props method to check the hashcode or equality of the instance of the class when compared with another one.

fromJson is the static method used by the client to build the item from the JSON decoded as a dynamic instance type. So, this class will the instance that contains the information for each item browsable on the App, simply news returned from the API.

Next, we’ll implement the Webservice Client moving in the repository folder.

On top of the Class we’ll declare the common properties for the class:

The API-KEY has to be provided to the Client to invoke the service in the right way, according to The Guardian API documentation. Note the @required and assert directive in the constructor used to say the client isn’t instantiable if the API key is null.

Next, we’ll model the http GET call to the search API:

The call returns a Future because the method will be executed in async mode. If the status code of the http response is 200 OK, the JSON will be decoded using a method provided by dart:convert. Looking to the json structure, the total number of available elements is provided in response > total so we’ll check the value navigating the dynamic object as a map

int itemCount = jsonResponse[‘response’][‘total’];print(‘Number of books about http: $itemCount.’);

If the list is empty we simply return an empty list

if (itemCount == 0) {  print(‘No items found for this topic’);  return [];}

Otherwise, for each allows building a list of “model instances” to be returned to the invoking layer:

  List<SearchApiItemModel> output = [];  for (var item in jsonResponse[‘response’][‘results’]) {    output.add(SearchApiItemModel.fromJson(item));  }  return output;

At this time the getItems returns the list of contents based on a topic. The topic is a string to be provided as a query of the search method.

The Repository class wraps the Client and provides, in the example, the API key for the web service:

class TheGuardianRepository {  TheGuardianClient client;  static const apikey = “ADD THE API KEY RECEIVED ON YOUT MAILBOX”;  
TheGuardianRepository() {
client = TheGuardianClient(apikey); }
Future<List<SearchApiItemModel>> getItems(String query) async {
return await client.getItems(query); }}

We are finally ready to implement the Bloc layer. First, we’ll create a folder in lib named bloc. We’ll define four classes inside:

  • NewsBloc
  • NewsEvents
  • NewsStates
  • BlocUtils

As described before, Bloc models a final state machine based on events. When an event is triggered, an event transition can happen, depending on the current context. Before describing the implementation, following the image that describes the state machine:

In the beginning, the content is empty. When the FETCH event is received, the web service is invoked to get the contents but the http call is async so we have a mutual transition in a loading state during the processing time. The results of the call will be (as a simplification for this article) loaded or error. Error means that something goes wrong, for instance, the status code returned is not 200. Loaded means that the content is available in memory to be displayed. To update the contents and make a new call to the service, you have the ability to trigger the refresh event. Not let’s model this state machine using the BLoC pattern.

We’ll start defining the states:

abstract class NewsState extends Equatable {  const NewsState();  @override
List<Object> get props => [];
}

NewsState is the abstract class used as a base for each state in the application so each state inherits from NewsState.

All state states for this sample are without properties except for the loaded state that contains the list of news retrieved from the services:

class NewsEmptyState extends NewsState {}class NewsLoadingState extends NewsState {}class NewsErrorState extends NewsState {}class NewsLoadedState extends NewsState {  final List<SearchApiItemModel> news;
NewsLoadedState({@required this.news}) : assert(news != null);
@override List<Object> get props => [news];}

No particular complexity here, just writes down one class for each state in your workflow, defining properties if needed.

For Events we’ll follow the same approach:

abstract class NewsEvent extends Equatable { const NewsEvent();}

NewsEvent is the abstract class as base for all the events.

class FetchNewsEvent extends NewsEvent {  final String topic;  FetchNewsEvent({@required this.topic}) : assert(topic != null);  @override  List<Object> get props => [topic];}
class RefreshNewsEvent extends NewsEvent { final String topic; RefreshNewsEvent({@required this.topic}) : assert(topic != null); @override List<Object> get props => [topic];}
class ResetNewsEvent extends NewsEvent { @override List<Object> get props => null;}
class ErrorNewsEvent extends NewsEvent { @override List<Object> get props => null;}

Each of the described events will be simplified as classes declaring arguments and properties when needed. For instance, as described when we implemented the client, the fetch event needs the topic to be passed to the search API as a query.

Well, we declared states and events but we’ll need to link each one to implement the designed workflow. The link between States and Events is the Bloc.

The class declaration links your states and events:

This is the core of the BLOC pattern. For each event received in the stream we can implement the logic needed for each particular event.

Note the particular keywords used in this class:

  • async*
  • yield*

Marking a function as async or async* allows it to use async/await keyword to use a Future.

The difference between both is that async* will always return a Stream and offer some syntax sugar to emit a value through yield keyword.

We already talk before about yield as a keyword similar to return but that cannot interrupt the function. The Stream is always opened during the application execution and yield allows to “return” a particular change consequence of the evaluation of an occurred event.

Why yield* and not yield? Because we separate the logic that checks each event in a separate method. This makes the code more readable and maintainable than put all your logic in one point.

So, looking at each method:

Stream<NewsState> _doReset(ResetNewsEvent event) async* {  yield NewsEmptyState();}

Reset just remove all the elements and move the state machine to the empty state (like what happens at the starting point).

Stream<NewsState> _doFetch(FetchNewsEvent event) async* {  yield NewsLoadingState();  try {    var items = await repository.getItems(event.topic);    yield NewsLoadedState(news: items);  } catch (error) {    print(error);    yield NewsErrorState();  }}
Stream<NewsState> _doRefresh(RefreshNewsEvent event) async* { yield NewsLoadingState(); try { var items = await repository.getItems(event.topic); yield NewsLoadedState(news: items); } catch (error) { print(error); yield NewsErrorState(); }}

Fetch and refresh are similar. The goal is to get the information to be displayed. When fetch is triggered, first the state is changed to loading:

yield NewsLoadingState();

Note yield works as a return but the process continues. Now the http call is made by the repository. If an error occurs (in the simple if the catch receives an exception), we’ll move to error state:

} catch (error) {  print(error);  yield NewsErrorState();}

And if the result is returned simply goes to Loaded:

var items = await repository.getItems(event.topic);yield NewsLoadedState(news: items);

The remaining class, BlocUtils, is used only to store useful pieces of cose reusable in the frontend layer. We choose to design this as a singleton, so the instance, when created, is persistent for all the duration of the process, without create a new instance in memory when the constructor will be invoked:

The method event causes the trigger of a new event in the bloc architecture. For instance, if you are in the EMPTY state and you’ll press a button, invoking this method you can trigger the fetch event to move the application to the next stage.

To summarize, at this point we implemented the data and the bloc layer of the application, using a bottom-up development approach. Now we’ll move to the frontend.

To make the Bloc available in the frontend, it has to be declared in the main function of the Ap. So, move on mail.dart and edit the build method:

The home section creates a new BlocProvider used to open the stream with the homepage through the News bloc. So, the HomePage is able to interact with the stream in both directions, from Frontend to data and from data to frontend.

At the beginning of the document, we anticipated that our App has two sections. The HomePage that lists the news and the Detail that allows reading the content of an article. To make the navigation possible, we declare the routes and we’ll use a “naming navigation”. Naming navigation means that each route in the App has a name and you can navigate to the specific section using the “name” as a unique key to identify the writing class to push in the navigation stack.

routes: {  HomePage.route: (context) => HomePage(),  NewsDetail.route: (context) => NewsDetail(),},

The implementation of the HomePage is quite long because all the UI is defined inside. If you need a specific explanation please add the request in the comments and I’ll publish a short article to explain that.

The HomePage is a stateful widget:

class HomePage extends StatefulWidget {  static final String route = “homepage”;  HomePage({Key key, this.title}) : super(key: key);  final String title;  @override  _HomePageState createState() => _HomePageState();}

The static field route is used to identify the routing name to open this page. At a certain point, in the UI, we link the portion that changes depending on bloc events declaring a BlocConsumer

The BlocConsumer, as suggested by the name, consumes events in the bloc stream. Checking the type of state class. When the state changes, the widget will the replaced to give the right experience: empty state means that we’ll display an Empty View, Loaded state the list view with the news, and so on.

Looking at the first view of the application, all the contents remain the same except for the widget inside the white container with rounded borders. In the image the Empty State.

Pressing the button, we’ll trigger the fetch event that moved to the Loading state (a simple Circular progress indicator):

OutlineButton(  onPressed: () {    BlocUtils().event(
context,
FetchNewsEvent(topic: “Mobile Trends”)
);
}, child: Padding( padding: const EdgeInsets.all(10.0), child: Text(“Click here to load contents”), ),)

Finally, then the news is loaded we can include the widget with the list view and display the contents;

In the beginning, the cast of the state allows converting to the right instance type and access to the list of news received by the bloc stream. For each element in the list, we’ll create a ListItem widget, defined as a custom widget in a separate class.

ListItem represents a card in the list. It displays the title and the topic of the news. On the right part, clicking on reading we’ll move to the detail page, clicking on share is possible to share the article using the apps available on the device.

TextButton( child: const Text(‘SHARE’), onPressed: () {  Share.share(news.webUrl, subject: news.webTitle); },),

Sharing with the share library is one line of code, putting the URL and the title of the content to Share.

To implement the navigation to the detail screen using a named route:

TextButton(  child: const Text(‘READ’),  onPressed: () {    Navigator.of(context).pushNamed(NewsDetail.route,    arguments: NewsDetailArgs(news));  }
),

NewsDetailsArgs allows passing the current instance of the news to be shown in the detail.

class NewsDetailArgs {  final SearchApiItemModel news;  NewsDetailArgs(this.news);}

The detail will be displayed in a web view using the WebUrl of the news through the flutter_inappwebview declared in the pubspec.yaml at the beginning of the article.

In conclusion, Bloc is a great pattern to implement layer separation in your flutter application with low impact in terms of lines of code. The separation with layers will be great and we’ll be able to implement complex interfaces without mixing logic in the frontend. Like other architectural patterns, probably is not the solution for all the applications you have in mind to implements but my advice is to try this particular variant available on flutter because very interesting and useful in the pocket of a Flutter Mobile Developer.

Full code of the project available in GitHub:

--

--

Gianni Deplano
Gianni Deplano

Written by Gianni Deplano

Senior Manager at EY Technology Consulting in Digital andEmerging technologies

No responses yet