Unlock the secrets of mastering the BLoC pattern in Flutter with TBR Group’s expertise.

image

Victoria Belyaeva

image

MVC, MVP, MVVM, Vanilla, Scoped Model, and BLoC Flutter… These are the architectural patterns, the models, which appear to be rather popular among software engineers nowadays.

But what type of mobile app structural organization to choose and which approach to the architecture should be applied?

This article will be centered around the practical approach of BLoC pattern implementation to the mobile crypto app niche.

The ABC of Flutter BLoC pattern

Being a declarative framework, Flutter implies using one of the state management approaches to build the UI and therefore reflect the current state of the application.

Flutter BLoC can be specified as a state management system belonging to the family of stream/observable-based patterns.

The main advantage of state management: it helps the developer to separate business logic from the presentation layer. It makes the codebase more clear, reusable and indepenend.

Key pros of BLoC over other state management models:

1. It’s a very popular state management system, and, as a result, there is big amount of live projects and a big dev community who is willing to share their experience.

2. It comes with easy-to-understand and well-detailed documentation. It contains lot’s of examples helpful to kick-start with BLoC.

3. Frequent updates and regular support from the authors.

4. It has the extension for Android Studion and VSCode that speed up the development process

We’re going to expand the topic, by articulating more practical aspects of this technology.

Using Flutter BloC for developing mobile apps: TBR Group’s case study

You can freely find the bitty official documentation and tons of info concerning the topic on the web.

But alongside such a diversity of information, there’s a lack of real case studies and Flutter BLoC examples.

We are going to demonstrate the BLoC pattern in action on the example of a single use case — a Crypto Aggregator mobile app.

Crypto Aggregator is a mobile app designed to browse through cryptocurrencies and market data.

The users can check how the data is changing with the help of the line and pie charts.

The Crypto Aggregator’s settings screen provides its users with a set of features allowing them to switch:

  • the fiat currency;
  • between the night and day mode;
  • the language of the application.

The Crypto Aggregator mobile app: the “Settings” page.

While implementing the BLoC pattern, the first step is to determine the states and events classes.

State implies the output data. Actually, the state class will represent what a user will see on the screen.

Event is the input data for the BLoC. It can act either as a trigger to start loading data. Or it can represent any actions performed by the user, such as pressing a button, a text input, a page scrolling, and so on.

The “state” class describes such finals as status, fiatCurrency, themeType, and error.

The “state” class describes such finals as status, fiatCurrency, themeType, and error.

@immutable
class SettingsState {
 final BlocStatus status;
 final String? fiatCurrency;
 final ThemeType? themeType;
 final Object? error;
 const SettingsState(
   this.status, {
   this.fiatCurrency,
   this.themeType,
   this.error,
 });
}
 
enum ThemeType {
 day,
 night,
}

Respectively:

  • status can be in a “Loading”, “Loaded” or “Error” state in order to show the loader, the loaded data, or an error message on the screen.
  • fiatCurrency is the currency selected by users from the dialog. The monetary value of the cryptocurrencies is shown according to that parameter.
  • themeType is the enum presented with 2 values: “day” and “night”. Changing this parameter updates the dark/light mode of the app.
  • error implies the data used to show the error message on the screen. Typically, it is merely a string and in case no errors occur, the field remains null.

As the language change with BLoC is covered by the “easy_localization” package, we have not handled it in the current implementation.

The “event” is shown in the picture below.

@immutable
@freezed
abstract class SettingsEvent with _$SettingsEvent {
 const SettingsEvent._();
 
 const factory SettingsEvent.getFiatCurrency() = GetFiatCurrencyEvent;
 
 const factory SettingsEvent.selectFiatCurrency(String fiatCurrency) =
     SelectFiatCurrencyEvent;
 const factory SettingsEvent.getTheme() = GetThemeEvent;
 
 const factory SettingsEvent.selectTheme(ThemeType themeType) = SelectThemeEvent;
}

The event is built with help of the “freezed” package. There are:

  • getFiatCurrency renders an event to load the currently selected currency.
  • selectFiatCurrency stands for an event that is called when a user selects a particular currency.
  • getTheme indicates an event to load the currently selected theme.
  • selectTheme refers to the event called when a user selects a particular theme.

As soon as the event and state are completed, we continue with the Settings BLoC class implementation as displayed on the screenshot.

class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
 final GetFiatCurrencyUseCase _getFiatCurrencyUseCase;
 final SelectFiatCurrencyUseCase _selectFiatCurrencyUseCase;
 final GetThemeUseCase _getThemeUseCase;
 final SelectThemeUseCase _selectThemeUseCase;

As the project is built with Clean Architecture, BLoC contains four use cases for each action.

 SettingsBloc(this._getFiatCurrencyUseCase, this._selectFiatCurrencyUseCase,
     this._getThemeUseCase, this._selectThemeUseCase)
     : super(
         const SettingsState(
           BlocStatus.Loading,
         ),
       ) {
   on<GetFiatCurrencyEvent>(_getFiatCurrency);
   on<SelectFiatCurrencyEvent>(_selectFiatCurrency);
   on<GetThemeEvent>(_getTheme);
   on<SelectThemeEvent>(_selectTheme);
 }

The BLoC constructor contains the initialization of the use cases and the initial state. The initial state can work as loading, which is the first state the users encounter, and the event handlers, which are extracted in separate methods.

Future<void> _getFiatCurrency(
   GetFiatCurrencyEvent event,
   Emitter<SettingsState> emit,

 ) async {
   emit(_loadingState());
   emit(await _getFiatCurrencyUseCase()
       .then(
         (String fiatCurrency) => SettingsState(
           BlocStatus.Loaded,

           fiatCurrency: fiatCurrency,

           themeType: state.themeType,

         ),

       )
       .catchError(_onError));
 }

Let’s take a look at the callback triggered on the GetFiatCurrency event. Initially, we emit the loading state to show the loader indicator. Just after the data is loaded, the new state appears. It contains the newly loaded fiat currency as well as the current theme type. To handle the errors, we catch them by means of using the _onError method.

Future<void> _selectFiatCurrency(
   SelectFiatCurrencyEvent event,
   Emitter<SettingsState> emit,
 ) async {
   emit(_loadingState());
   emit(await _selectFiatCurrencyUseCase(event.fiatCurrency)
       .then(
         (String fiatCurrency) => SettingsState(
           BlocStatus.Loaded,
           fiatCurrency: event.fiatCurrency,
           themeType: state.themeType,
         ),
       )
       .catchError(_onError));
 }

Now, let’s explore the callback triggered by the SelectFiatCurrency event. The algorithm is mostly the same as the on “_getFiatCurrency” method. The only difference lies in the fact that the fiat currency on the loaded state is received from the event.

SettingsState _loadingState() => SettingsState(BlocStatus.Loading,
     fiatCurrency: state.fiatCurrency, themeType: state.themeType);
 
 Future<SettingsState> _onError(Object error) async => SettingsState(
       BlocStatus.Error,
       fiatCurrency: state.fiatCurrency,
       themeType: state.themeType,
       error: error,
     );

_loadingState and _onError methods help to simply create the error and respectively loading states.

The last step is to integrate the SettingsBloc into the app screen. To meet such a purpose, flutter_bloc provides a particular list of widgets.

The default widget is BlocBuilder. However, we use BlocSelector on this screen, since it allows us to focus on particular fields. Thus, if a user updates the fiat currency, there is no reason to rebuild the Day/Night mode switcher.

At first, comes the BLoC initialization.

 final SettingsBloc settingsBloc = di.sl.get();

 
 @override
 void initState() {
   super.initState();
   settingsBloc.add(const SettingsEvent.getFiatCurrency());
 }

Before the page is built, we need to add SettingsEvent.getFiatCurrency to load the selected fiat currency. In the project, we use get_it for the BLoC dependency injection, but the more usual way is to use BlocProvider.

Next comes the fiat currency switcher.

GestureDetector(
   onTap: () async {
     final String? selectedCurrency =
         await showBottomSheetCurrencySelector(
             context: context);
     if (selectedCurrency != null) {
       settingsBloc.add(
           SettingsEvent.selectFiatCurrency(selectedCurrency));
     }
   },
   child: Row(
     children: <Widget>[
       BlocSelector<SettingsBloc, SettingsState, String>(
         bloc: settingsBloc,
         selector: (SettingsState state) =>
             state.fiatCurrency ?? '-',
         builder: (BuildContext context, String fiatCurrency) {
           return Text(
             fiatCurrency.toString().toUpperCase(),
             style: TextStyles.overlay3Bold14,
           );
         },
       ),
       SizedBox(width: 4.w),
       const ChevronIcon(),
     ],
   ),
 ),

As soon as a user selects the currency from the bottom sheet, we need to send it to the BLoC wrapped with the SettingEvent.selectFiatCurrency event.

On the selector method, we are able to receive the required data. If the string returned from the selector method changes, everything inside the builder method is rebuilt.

And the final is the Day/Night mode switcher.

 BlocSelector<SettingsBloc, SettingsState, ThemeType?>(
   bloc: settingsBloc,
   selector: (SettingsState state) => state.themeType,
   builder: (BuildContext context, ThemeType? themeType) {
     return GestureDetector(
       onTap: () {
         if (themeType == ThemeType.night) {
           settingsBloc.add(
               const SettingsEvent.selectTheme(ThemeType.day));
         } else {
           settingsBloc.add(
               const SettingsEvent.selectTheme(ThemeType.night));
         }
       },
       child: Row(
         mainAxisAlignment: MainAxisAlignment.spaceBetween,
         children: <Widget>[
           Text(
             themeType == ThemeType.night
                 ? 'switch_to_day_mode'.tr()
                 : 'switch_to_night_mode'.tr(),
             style: TextStyles.semiBold14
                 .copyWith(color: Theme.of(context).hintColor),
           ),
           Icon(
             themeType == ThemeType.night
                 ? CupertinoIcons.sun_max
                 : CupertinoIcons.moon,
             color: Theme.of(context).hintColor,
           )
         ],
       ),
     );
   },
 ),

It works in the same simple way as the fiat currency switcher does. On tap, BLoC updates the state with the new themeType that updates the corresponding text and icon.

The following screenshots demonstrate the UI updates. There come selecting fiat currency and updating the color mode.

The Crypto Aggregator mobile app: “Selecting fiat currency” option.

The Crypto Aggregator mobile app: “Updating the color mode” option.

The Crypto Aggregator project is based on Clean Architecture and the BLoC state management. You can check our GitHub repository, where extra details on the topic have been given out.

Flutter cross-platform has proved to be applicable for crafting mobile apps for meeting various purposes.

You can get more insights on this topic from our articles:
The fundamentals of Flutter & Dart, and Why choose Flutter for mobile app development in 2023?

TBR Group has carried out such software implementations as:
VinciLabs — a platform for providing digital medical solutions for healthcare entities;
SwishBOOM and Babymates are child care mobile apps;
pingNpay will describe the digital payment options based on blockchain technology;
Wywroty Śpiewnik — a musical mobile app — will make you privy to the Polish guitar songs’ performance.

A complete list of the designed mobile apps is presented on our Case Study page.

Summary

Operating in the sphere of platform-independent mobile app engineering, TBR Group targets Flutter app development.

Web, iOS, and Android mobile app developers are welcome to get in touch with TBR Group.

Whether you are an experienced engineer, a junior coder, or an enterprise willing to establish your presence online — feel free to contact TBR Group and articulate any concept directly with the development team.

Feel free to contact TBR Group and articulate any concept
directly with the dev team.

Share your idea with us, and we’ll come up with the best development solution for your case.

Get in touch today