bloc_pattern 2.5.1

  • Readme
  • Changelog
  • Example
  • Installing
  • 99

Bloc Pattern #

Donate

Package that helps you implementing BloC Pattern by Dependency Injection in your project.

Package #

bloc_pattern

What is BloC? #

BLoC stands for Business Logic Components. The gist of BLoC is that everything in the app should be represented as stream of events: widgets submit events; other widgets will respond. BLoC sits in the middle, managing the conversation. It will be created separately from the view, isolating the logic of the code.

Why to use bloc_pattern? #

It's perfect to organize, and follow the best practices in your code, taking vantage of Dependency Injection. And it's the best package to use with slidy (created to structure your Flutter project).

How to implement? #

First step. #

Add bloc_pattern in your pubspec.yaml.

dependencies:

  bloc_pattern: ^2.3.2

Or you can use slidy to add in your dependencies:

slidy install bloc_pattern

Starting to code #

1. #

Create the BloC class of your module, and extends from BlocBase.

import 'package:bloc_pattern/bloc_pattern.dart';
import 'package:rxdart/rxdart.dart';

class CounterBloc extends BlocBase{

  Observable<int> counter; // observable (Stream)

  CounterBloc() {
    counter = Observable.merge([ //merges the both streams 
      _increment,
      _decrement,
    ]).startWith(0) //starts with the value 0(the initial data)
    .scan((acc, curr, i) => acc + curr, 0 /* inital value: 0 */) // scans the old(acc) and the current(curr) value, and sum them
    .asBroadcastStream(); //turns the stream into a Broadcast straem(it can be listened to more than once)
  }

  final _increment = new BehaviorSubject<int>(); //the BehaviorSubject gets the last value
  final _decrement = new BehaviorSubject<int>();

  void increment() => _increment.add(1); //method to increment
  void decrement() => _decrement.add(-1);//method to decrement


@override
  void dispose() {// will be called automatically 
    _increment.close();
    _decrement.close();
  }

}

2. #

Now wrap your MaterialApp into a BlocProvider. Obs.: BlocProvider is the widget where you can Inject all the BloCs, and then recover them anywhere in your application.

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
        blocs: [
          Bloc((i) => CounterBloc()),
      ],
          child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(title: 'Flutter Demo Home Page'),
      ),
    );
  }
}
...

Recovering the BloC class. #

    //recovering your BloC
  final bloc = BlocProvider.getBloc<CounterBloc>();

Using StreamBuilder #

The StreamBuilder widgets lets you change the UI reactively without needing to call setState()(that rebuilds the stateful widget);

StreamBuilder(
                stream: bloc.outCounter,  //here you call the flux of data(stream)
                builder: (BuildContext context, AsyncSnapshot snapshot) {
                  return Text(
                    '${snapshot.data}',
                    style: Theme.of(context).textTheme.display1,
                  );
                },
              ),

Consuming the BloC directly from the Widget #

You can consume your BloC directly from the target Widget using the Consumer() class. Everytime the CounterBloc adds a new value, the widgets within the Consumer will have new data. You can see the project source code here.

BloC using Consume(): #

class CounterBloc {
    ...
int counter = 0;
onChanged(){
  counter++;
  notifyListeners(); //notifies when occurs a change
}

}

 Consumer<CounterBloc>(
            builder: (BuildContext context, CounterBloc bloc) {
              return Text(bloc.counter.toString()); //calling the counter value
          ),
          SizedBox(
            height: 25.0,
          ),
          Consumer<CounterBloc>(
            builder: (BuildContext context, CounterBloc bloc) {
              return 
            RaisedButton(
              onPressed: () {
                bloc.onChanged(); //calling onChanged() that will increment the value
              },
              child: Icon(Icons.add),
            );
            },
          ),

Dependency Injection #

You can also inject other dependencies aside BloC:

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      child: MaterialApp(
        home: IncrementWidget(),
      ),
      //add yours BLoCs controlles
       blocs: [
        Bloc((i) => IncrementController(i.get<GeneralApi>({"name":"John"}))),
        Bloc((i) => DecrementController())
      ],
      //add Other Object to provider
      dependencies: [
        Dependency((i) => GeneralApi(i.params['name'])), //repository
      ],
    );
  }

You can define if the dependency will be a Singleton or not:

Bloc((i) => CounterBloc(), singleton: false)

To inject the dependency in your class use:


@override
  Widget build(BuildContext context) {
   
    //recovering your API dependency
  final GeneralApi api = BlocProvider.getDependency<GeneralApi>(); //repository
  
  //Passing Data through parameters
  final UserModel user = BlocProvider.getDependency<UserModel>({
    "id": 1,
    "name": "João"
  });
  ....
}

Tags #

You can create new BlocProviders independently.

  • Use the property "tagText" giving a name for your new BlocProvider.
  • When you have more than one BlocProvider, you will need to use its tag to indentificate, otherwise it should return an error.

...
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      //tag module
      tagText: "newModule",
  ...

Calling the dependencies and BloCs from other classes:


    BlocProvider.tag("newModule").getBloc<BlocController>();
    ...

ModuleWidget #

The ModuleWidget uses Tag Module in its structure:

  • Implicity creates a tag for the module class.
  • Gets automatically the tag when you call the module BloC.

You can use Slidy to create the module

class HomeModule extends ModuleWidget {

  //Inject the blocs
  @override
  List<Bloc<BlocBase>> get blocs => [
        Bloc((i) => IncrementController())),
        Bloc((i) => DecrementController())
      ];

  //Inject the dependencies
  @override
  List<Dependency> get dependencies => [
        Dependency((i) => GeneralApi(i.params['name'])),
      ];

  //main widget
  @override
  Widget get view => HomeWidget();

  //shortcut to pick up dependency injections from this module
  static Inject get to => Inject<HomeModule>.of();

}

So instead of using BlocProvider and tags, you can just use ModuleWidget, it will also organize and modularize your project.

  //use
  HomeModule.to.bloc<HomeBloc>();
  //instead of
  BlocProvider.tag("HomeModule").bloc<HomeBloc>();

Dispose #

It's important to always call the dispose(), to make sure that the objects won't continue processing when you don't have any data.

The BlocBase already comes with Disposable, you just need to override it and will be called automatically in your ModuleWidget

class YourBloc extends BlocBase {

  @override
  void dispose(){
    super.dispose
    //dispose Objects
  }
}

To do this manually or restart some injected singleton, use:

//dispose BLoC
BlocProvider.disposeBloc<BlocController>();

//dispose dependency
BlocProvider.disposeDependency<GeneralApi>();

//dispose BLoC in Module
BlocProvider.tag("HomeModule").disposeBloc<BlocController>();

//dispose BLoC in ModuleWidget
HomeModule.to.disposeBloc<BlocController>();

[Optional] Extends Disposable in your service or repository for automatic dispose.


class Repository extends Disposable {

  @override
  void dispose(){
    //dispose Objects
  }
}

Tests #

You can start your modules in the test environment and use dependency injections directly.

...
import 'package:flutter_test/flutter_test.dart';

void main() {
  //start Module and Dependency Injection
  initModule(AppModule());
  AppBloc bloc;

  setUp(() {
    //get bloc
    bloc = AppModule.to.bloc<AppBloc>();
  });

  group('AppBloc Test', () {
    test("Counter Test", () {
      expect(bloc.counter, 1);
    });
    test("Class Test", () {
      expect(bloc, isInstanceOf<AppBloc>());
    });
  });
}

You can also override injection elements in initModule. Use this to replace your client with a Mock.

import 'package:flutter_test/flutter_test.dart';

class MockClient extends Mock implements Dio {}

void main() {
  //start Module and Dependency Injection
  initModule(AppModule(), changeDependencies: [
      Dependency((i) => MockClient() as Dio),
  ]);
}

You can create your mocks based on the "BlocProvider.isTest" static property, which returns a boolean.

For more information #

Access Flutterando Blog.

2.5.1 (Nov 14, 2019) #

  • Fix dispose module error

2.5.0 (Nov 08, 2019) #

  • Added generic support for any class bloc

2.4.4 (Nov 01, 2019) #

  • Added change inject in initModule (tests)

2.4.2+1 (Out 25, 2019) #

  • Added bloc_pattern_test from tests.

2.4.1+1 (Out 21, 2019) #

  • Fix Module Not Found error

2.4.0+2 (Out 06, 2019) #

  • Added distinct parameter in Consumer Widget and condition to call builder. Consumer
  • Added specific Consumer for Modules Pattern ConsumerModule<HomeModule, HomeBloc>(...);

2.3.4 (Out 03, 2019) #

  • Fix error singleton blocs module
  • Added debugMode false to print texts

2.3.3 (Sep 26, 2019) #

  • Fix error in segments bloc

2.3.2 (Sep 13, 2019) #

  • Fix multiple tags error

2.3.1 (Aug 24, 2019) #

  • Mapped Errors in Custom Exception

2.3.0 (Aug 23, 2019) #

  • Mapped Errors in Custom Exception

2.2.3 (Jul 11, 2019) #

  • Fix error dispose unique bloc

2.2.2+3 (Jun 28, 2019) #

  • Add ConsumerModule

2.2.1 (Jun 22, 2019) #

  • Prepare to Slidy
  • Fix error inject module.

2.2.0+5 (Jun 21, 2019) #

  • Fix error consumer module.

2.2.0+4 (Jun 19, 2019) #

  • Fix error dispose bloc.

2.2.0+1 (Jun 14, 2019) #

  • Introduced project modules.
  • Tag for each BlocProvider. You can use multiple BlocProvider independently.

2.1.9+2 #

  • Fix dispose error

2.1.9 #

  • Add Consumer pattern.

2.1.7 #

  • Corrections in the parameters.

2.0.1 (May 1, 2019) #

  • BlocProvider and BlocProviderList are now one.
  • Added Injection of BLoC dependencies.
  • Added Injection Dependencies.

1.1.2 (December 8, 2018) #

  • removed context injection

1.1.1 (December 8, 2018) #

  • removed context injection

1.1.0 (November 19, 2018) #

  • Injected the context (BuildContext) into the controller.

1.0.0 (November 19, 2018) #

  • Support for Dart 2.1
  • Enhanced Bloc lifecycle with fast access anywhere in the application.

0.1.0 (November 8, 2018) #

  • Provider with StatefulWidget.

example/README.md

Bloc Pattern #

Donate

Tools to implement BLoC Pattern with Dependency Injection in your project

Start #

Add bloc_pattern in your pubspec.yaml.

BLoC class #

Create the business logic class and extend to BlocBase

import 'package:bloc_pattern/bloc_pattern.dart';

class ValueBloc extends BlocBase {
  double value = 0.0;

  onChangeValue(double v) {
    value = v;
    notifyListeners();
  }
}

Now add the BlocProvider widget before MaterialApp

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      //add yours BLoCs controlles
      blocs: [
        Bloc((i) => ValueBloc()),
      ],
      //your main widget 
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: HomePage(),
      ),
    );
  }
}
...

You can search for a BLoC class anywhere in your application using:

    //recovering your Bloc
  final ValueBloc bloc = BlocProvider.getBloc<ValueBloc>();

Or consume directly on the Target widget using Consumer

    //Cosume your BLoC
  Consumer<ValueBloc>(
    builder: (BuildContext context, ValueBloc valueBloc) {
      return _textValue(valueBloc.value);
    },
  ),
  Container(
    height: 25,
  ),
  Consumer<ValueBloc>(
    builder: (BuildContext context, ValueBloc valueBloc) {
      return Slider(
      activeColor: Colors.white,
      inactiveColor: Colors.white,
      min: 0.0,
      max: 1.0,
      onChanged: valueBloc.onChangeValue,
      value: valueBloc.value);
    },
  ),

In this way, every time the ValueBloc onChangeValue method, the widgets inside the consumer will have new data. acesse o projeto completo clicando aqui.

Using Streams and Reactive Programming (Rx) #

You can also use the provider to get BlocClasses that work with streams for more complex processing using reactive programming.

import 'dart:async';
import 'package:bloc_pattern/bloc_pattern.dart';
import 'package:rxdart/rxdart.dart';

class BlocController extends BlocBase {

BlocController();

//Stream that receives a number and changes the count;
var _counterController = BehaviorSubject<int>.seeded(0);
//output
Stream<int> get outCounter => _counterController.stream;
//input
Sink<int> get inCounter => _counterController.sink;

increment(){
    inCounter.add(_counterController.value+1);
}

//dispose will be called automatically by closing its streams
@override
void dispose() {
  _counterController.close();
  super.dispose();
}

}

Add the Provider in the main widget of your widget tree by passing as your BlocController parameter


...

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      child: MaterialApp(
        home: IncrementWidget(),
      ),
      blocs: [
        //add yours BLoCs controlles
        Bloc((i) => BlocController()),
      ],
    );
  }
}

...

Now you can recover your Bloc anywhere in your widget tree with the help of BlocProvider


@override
  Widget build(BuildContext context) {
    //recovering your Bloc
  final BlocController bloc = BlocProvider.getBloc<BlocController>();

  ....


}

Now just use StreamBuilder to get your streams and change the UI without needing setState


StreamBuilder(
    stream: bloc.outCounter,
    builder: (BuildContext context, AsyncSnapshot snapshot) {
    return Text(
        '${snapshot.data}',
        style: Theme.of(context).textTheme.display1,
    );
    },
),

  ....

floatingActionButton: new FloatingActionButton(
    onPressed: bloc.increment,
    tooltip: 'Increment',
    child: new Icon(Icons.add),
), 


}

Dependency Injection #

Just like BLoC, you can also include in dependency injection other class. Ex: Services and Models


...

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      child: MaterialApp(
        home: IncrementWidget(),
      ),
      //add yours BLoCs controlles
       blocs: [
        Bloc((i) => IncrementController(i.get<GeneralApi>({"name":"John"}))),
        Bloc((i) => DecrementController())
      ],
      //add Other Object to provider
      dependencies: [
        Dependency((i) => GeneralApi(i.params['name'])),
      ],
    );
  }
}

...

You can define whether this dependency will behave as a singleton or not. Default is false.

For injection, use:


@override
  Widget build(BuildContext context) {
   
    //recovering your API dependency
  final GeneralApi api = BlocProvider.getDependency<GeneralApi>();
  
  //Passing Data by Parameters
  final UserModel user = BlocProvider.getDependency<UserModel>({
    "id": 1,
    "name": "João"
  });
  ....
}

Dispose #

The data is automatically discarded when the application finishes, however if you want to do this manually or restart some injected singleton, use:

//dispose BLoC
BlocProvider.disposeBloc<BlocController>();

//dispose dependency
BlocProvider.disposeDependency<GeneralApi>();

[Optional] Add the dispose to your Bloc so that it can be called automatically or manually.


class YourBloc extends BlocBase {

  @override
  void dispose(){
    super.dispose
    //dispose Objects
  }
}

[Optional] Extend you Service or Repositore with Disposable for automatic dipose.


class GeneralApi extends Disposable {

  @override
  void dispose(){
    //dispose Objects
  }
}

THAT´S ALL

Para mais informações #

Acesse o Blog do Flutterando Clicando aqui.

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  bloc_pattern: ^2.5.1

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:bloc_pattern/bloc_pattern.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
98
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
99
Learn more about scoring.

We analyzed this package on Dec 9, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.6.1
  • pana: 0.12.21
  • Flutter: 1.9.1+hotfix.6

Platforms

Detected platforms: Flutter

References Flutter, and has no conflicting libraries.

Health suggestions

Format lib/bloc_pattern_test.dart.

Run flutter format to format lib/bloc_pattern_test.dart.

Format lib/src/bloc_provider_error.dart.

Run flutter format to format lib/src/bloc_provider_error.dart.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.0.0-dev.68.0 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.11 1.14.12
meta 1.1.7 1.1.8
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
bloc ^2.0.0
flutter_bloc ^2.0.0
flutter_mobx ^0.3.3+1
flutter_test
mobx ^0.3.9+1
rxdart ^0.22.6