flutter_bloc_pattern

Base class, BLoC provider and rxdart builder for BLoC pattern in Flutter.

Flutter test Pub Pub codecov GitHub Style Hits

Getting Started

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

dependencies:
  flutter_bloc_pattern: <latest_version>

2. Implements BaseBloc

import 'package:disposebag/disposebag.dart';
import 'package:flutter_bloc_pattern/flutter_bloc_pattern.dart';
import 'package:rxdart_ext/rxdart_ext.dart';

class MyBloc implements BaseBloc {
  StateStream<String> get stream;

  @override
  void dispose() {}
}

3. Consume BLoC

 final bloc = BlocProvider.of<MyBloc>(context);
 return RxStreamBuilder<String>(
  stream: bloc.stream,
  builder: (context, state) {
    return ...;
  },
);

Example: A port of the standard "Counter Button" example from Flutter

1. File counter_bloc.dart

import 'dart:async';

import 'package:disposebag/disposebag.dart';
import 'package:flutter_bloc_pattern/flutter_bloc_pattern.dart';
import 'package:rxdart_ext/rxdart_ext.dart';

class CounterBloc extends DisposeCallbackBaseBloc {
  /// Inputs
  final VoidAction increment;

  /// Outputs
  final StateStream<int> state;

  CounterBloc._({
    required VoidAction dispose,
    required this.increment,
    required this.state,
  }) : super(dispose);

  factory CounterBloc() {
    final incrementController = StreamController<void>();

    final state$ = incrementController.stream
        .scan<int>((acc, _, __) => acc + 1, 0)
        .publishState(0);

    return CounterBloc._(
      dispose: DisposeBag([incrementController, state$.connect()]).dispose,
      increment: incrementController.addNull,
      state: state$,
    );
  }
}

2. File main.dart

import 'package:example/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc_pattern/flutter_bloc_pattern.dart';

class TextCounter1 extends StatelessWidget {
  const TextCounter1({super.key});

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

    return RxStreamBuilder<int>(
      stream: bloc.state,
      builder: (context, state) {
        return Text(
          'COUNTER 1: $state',
          style: Theme.of(context).textTheme.titleLarge,
        );
      },
    );
  }
}

class IncrementButton extends StatelessWidget {
  const IncrementButton({super.key});

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

    return FloatingActionButton(
      onPressed: bloc.increment,
      tooltip: 'Increment',
      child: const Icon(Icons.add),
    );
  }
}