Preview

Count up application of BLoC Architecture

What”s BLoC😏

BLoC is the architecture announced at Dart Conf 2018.

This is YouTube Video of BLoC architecture
https://www.youtube.com/watch?v=PLHln7wHgPE

BLoC is Business Logic Component.

In other words, clearly distinguish the business logic from the screen view.

Guidelines🔰

BLoC Architecture is four Guidelines.

  1. BLoC’s input and output interfaces are all Stream/Sink
  2. BLoC’s dependencies are always injectable and environment independent
  3. There is no conditional branch for each environment in BLoC
  4. Implementation is free a long as it complies with above rule

If you want to know a Detailed explanation, you watch This Youtube Video.

https://www.youtube.com/watch?v=RS36gBEp8OI

Source code🖥

Create Count up Applications

Preview

We create Count up Application with BLoC.

If user tap a Floating Button, Count up the centering character.

Create BLoC class

import 'dart:async';

import 'package:bloc_sample_app/BlocProvider.dart';

class IncrementBloc implements BlocBase {

  int _counter;

  // handle controller
  StreamController<int> _counterController = StreamController<int>();
  StreamSink<int> get _inAdd => _counterController.sink;
  Stream<int> get outCounter => _counterController.stream;

  // handle view action
  StreamController<int> _actionController = StreamController<int>();
  StreamSink<int> get incrementCounter => _actionController.sink;

  IncrementBloc() {
    _counter = 0;
    _actionController.stream.listen(_handleLogic);
  }

  void _handleLogic(data) {
    _counter += 1;
    _inAdd.add(_counter);
  }

  @override
  void dispose() {
    _counterController.close();
    _actionController.close();
  }
}

BLoC Architecture Feature is input and output interfaces are all Stream/Sink. then, this class management Input and Output.

  • Sink: Input Event
  • Stream : Output Event

_actionController in constructor waits Sink(Input) if Stream(Output) changes. In Other words, if _actionController.sink.add is executed, _handleLogic is called. Then character is counted up.

Create View class

Next, Let’s detect View input / output events from BLoC class

import 'dart:async';

import 'package:bloc_sample_app/BlocProvider.dart';
import 'package:bloc_sample_app/minimum/IncrementBloc.dart';
import 'package:flutter/material.dart';


class MinimumStateless extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider<IncrementBloc>(
      bloc: IncrementBloc(),
      child: Minimum(),
    );
  }
}

class Minimum extends StatefulWidget {
  @override
  _MinimumState createState() => new _MinimumState();
}

class _MinimumState extends State<Minimum> {
  int counter = 0;

  @override
  Widget build(BuildContext context) {
    final IncrementBloc bloc = BlocProvider.of<IncrementBloc>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text("最小のBLoC"),
      ),
      body: Center(
        child: StreamBuilder<int>(
            stream: bloc.outCounter,
            initialData: counter,
            builder: (context, snapshot) {
              return Text("${snapshot.data}");
            }),
      ),
      floatingActionButton:
          FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () {
                bloc.incrementCounter.add(counter);
              }),
    );
  }
}

As other BLoC rule’s, BLoC’s dependencies are always injectable and environment independent. So, We will inject dependency with BlocProvider.

body: Center(
   child: StreamBuilder<int>(
       stream: bloc.outCounter,
       initialData: counter,
       builder: (context, snapshot) {
          return Text("${snapshot.data}");
        }),
),

In this part, We will setting output event with StreamBuilder class.

floatingActionButton:
          FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () {
                bloc.incrementCounter.add(counter);
              }),

In this part, if user taps floating button, Input Event occurs. If Input event occurs, Stream(output event) defined in the constructor of Bloc class runs and count up centering character.

BlocProvider


import 'package:flutter/cupertino.dart';

abstract class BlocBase {
  void dispose();
}

// Generic BLoC provider
class BlocProvider<T extends BlocBase> extends StatefulWidget {
  BlocProvider({
    Key key,
    @required this.child,
    @required this.bloc,
  }): super(key: key);

  final T bloc;
  final Widget child;

  @override
  _BlocProviderState<T> createState() => _BlocProviderState<T>();

  static T of<T extends BlocBase>(BuildContext context){
    final type = _typeOf<BlocProvider<T>>();
    BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
    return provider.bloc;
  }

  static Type _typeOf<T>() => T;
}

class _BlocProviderState<T> extends State<BlocProvider<BlocBase>>{
  @override
  void dispose(){
    widget.bloc.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context){
    return widget.child;
  }
}

Benefit of the Bloc architecture😋

By adopting this architecture is view and business logic clearly separately. And It becomes easy to write Unit test.

For example, If We add function of「I want to display Science as a multiple of 3 when counting up」, we write _handleLogic method to unit test.

If business logic is written on View class, we are difficult to write Unit test.

Sample code💻

I publish sample code.

https://github.com/yshogo/Flutter_BLoC_pattern_sample

Preview