Skip to content

Apenas um package com bases para implantar o Bloc no seu Código

License

Notifications You must be signed in to change notification settings

jacobaraujo7/bloc-pattern

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.

About

Apenas um package com bases para implantar o Bloc no seu Código

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published