Using BLoC in Flutter Redux

Using BLoC in Flutter Redux

After using flutter for some time, it seems there are no the go-to way to handle ... Bloc /// /// like reducers in redux, but don't return a new state.

State management is a huge issue for the Flutter community. Coming from an Angular background I got accustomed to using Redux and therefore it made sense for me to follow the same pattern in Flutter. I also used services to encapsulate business logic and expose the state to the UI. It was a good exercise for me to try and emulate the same architecture in Flutter, especially with the emerging of the BLoC pattern.

The following article is my personal take on the matter, I don’t claim it to be the only way of achieving this architecture.

The BLoC pattern

The Business Logic Component pattern or as it is widely known the BLoC pattern is an architecture designed to decouple business logic from the view. It has helped teams to have a single codebase in regards to their business logic and expose them in various frameworks, most notable AngularDart and Flutter

In the BLoC pattern, you create a component that interfaces with the view only through Streams. This goes for all interaction either coming into the BLoC or coming out of it. The outgoing communication is straight forward as you only need to wrap your view Widget in a StreamBuilder and listen to the values coming out of the exposed outgoing stream.

class ExampleBloc {
     final StreamController<int> _outController =  StreamController<int>();

      int get out => _outController.stream;
    }


    ...

    StreamBuilder<int>(
      stream: exampleBloc.out
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return Container();
        }
        final out = snapshot.data;
        return Text('$out');
      }
    )

For the incoming communication, you have to also expose a Sink in which the view will push events from the user. In your BLoC, you subscribe to the incoming Stream and delegate the action either to a private method or a Repository, if it is about fetching data.

class ExampleBloc {
     int counter = 0;
     final StreamController<int> _incrementController =  StreamController<int>();
     final StreamController<int> _outController =  StreamController<int>();

     ExampleBloc() {
        _outController.sink.add(counter);
        _incrementController.listen(_incrementBy);
      }

     /// Do the actions or delegate in a repository
     void _incrementBy(int value) {
       counter += value;
       //Notify subscribers
       _outController.sink.add(counter);
     }

      int get out => _outController.stream;
      int get incrementBy => _incrementController.sink;
    }


    ...

    FloatingActionButton(
      onPressed: () => exampleBloc.incrementBy.add(1),
      tooltip: 'Increment By 1',
    )

What remains to be tackled is the state keeping as we really do not want to keep it in our BLoC.

Redux Store with BLoC

As we mentioned before the BLoC should not be responsible to keep the application’s state. It should delegate this responsibility to some other component dedicated to doing state management. In this example, we will use the Redux Store.

The BLoC could funnel events coming from the Sink to the Store as Actions and connect the application state from the Store to the Stream so that the Widget gets it.

/// The action to be dispatched from the BLoC to the Store
    class IncrementByAction {
      final int value;
      const IncrementByAction(this.value);
    }


    /// The state of the application as it is managed by the Store
    class AppState {
      final int counter;

      const AppState(this.counter);

      factory AppState.initial() => AppState(0);

      AppState incrementBy(int value) => AppState(counter+value);
    }


    final store = new Store<AppState>(
      TypedReducer<AppState, ChangeLocaleAction>((AppState state, IncrementByAction action) => state.incrementBy(action.value)),
      initialState: AppState.initial(),
      middleware: [],
    );


    /// The BLoC delegates all state management to the Store
    class ExampleBloc {
     final Store<AppState> store;
     final StreamController<int> _incrementController =  StreamController<int>();

     ExampleBloc(this.store) {
       _incrementController.listen(_incrementBy);
     }

     /// Dispatch the Store Action to change the AppState
     void _incrementBy(int value) {
       store.dispatch(IncrementByAction(value));
     }

      /// To connect the state from the store to the ougoing stream of the BLoC subscribe to store.onChange
      int get out => this.store.onChange.map((AppState state) => state.counter);
      int get incrementBy => _incrementController.sink;
    }
    /*
     * No changes are needed in the UI as the BLoC's api did not change.
     */
    ...
    FloatingActionButton(
      onPressed: () => exampleBloc.incrementBy.add(1),
      tooltip: 'Increment By 1',
    )
    ...
    StreamBuilder<int>(
      stream: exampleBloc.out
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return Container();
        }
        final out = snapshot.data;
        return Text('$out');
      }
    )

The obvious benefit of using this approach is that if you do not have to change the BLoC’s API and thus no change in the UI (Widgets) is needed.

However, there is another approach that kind of breaks the BLoC all Streams contract. It uses methods for actions and getters for state subscribing. It is all made possible by the use of StoreConnector.

/// The action to be dispatched from the BLoC to the Store
    class IncrementByAction {
      final int value;
      const IncrementByAction(this.value);
    }


    /// The state of the application as it is managed by the Store
    class AppState {
      final int counter;

      const AppState(this.counter);

      factory AppState.initial() => AppState(0);

      AppState incrementBy(int value) => AppState(counter+value);
    }


    final store = new Store<AppState>(
      TypedReducer<AppState, ChangeLocaleAction>((AppState state, IncrementByAction action) => state.incrementBy(action.value)),
      initialState: AppState.initial(),
      middleware: [],
    );


    /// The BLoC acts like a proxy to the store
    class ExampleBloc {
     final Store<AppState> store;

     ExampleBloc(this.store);

      /// To connect the state from the store to the ougoing stream of the BLoC subscribe to store.onChange
      int get counter => this.store.state.counter;
      void incrementBy(int value) {
          store.dispatch(IncrementByAction(value));
      }
    }
    ...
    StoreConnector<AppState, DebtorBloc>(
        converter: (store) => ExampleBloc(store),
        builder: (BuildContext context, ExampleBloc bloc) {
          return FloatingActionButton(
            onPressed: () => exampleBloc.incrementBy(1),
            tooltip: 'Increment By 1',
          );
        }
    )
    ...
    StoreConnector<AppState, DebtorBloc>(
        converter: (store) => ExampleBloc(store),
        builder: (BuildContext context, ExampleBloc bloc) {
          final out = bloc.counter;
          return Text('$out');
        }
    )

The BLoC pattern is a great way to encapsulate business logic and Redux is a great state management paradigm. Combining the two of them can create a clean logic layer in your application, which is going to help you tremendously in creating clean UI code.

flutter redux mobile-apps

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Google's Flutter 1.20 stable announced with new features - Navoki

Google has announced new flutter 1.20 stable with many improvements, and features, enabling flutter for Desktop and Web

Top 25 Flutter Mobile App Templates in 2020

Flutter has been booming worldwide from the past few years. While there are many popular mobile app development technologies out there, Flutter has managed to leave its mark in the mobile application development world. In this article, we’ve curated the best Flutter app templates available on the market as of July 2020.

How To Succeed In Mobile App Wireframe Design?

This article covers everything about mobile app wireframe design: what to do and what not, tools used in designing a mobile or web app wireframe, and more.

How much does it cost to make a Flutter app for your business?

Get a Free Quote on Android App Development, iPhone App Development, Ionic App Development, Video Development, ASO, SEO, Google Ads/Adwords, SEO for your app Idea.

Flutter – A Boon to Mobile App Developers

Flutter app development is in trend; ever since flutter - the mobile app development framework, has been helping app developers to build amazing applications.