Bloc Orchestration in Flutter

Mario Gunawan
Flutter Tips
Published in
4 min readDec 8, 2022
Original Photo by Arturo Añez: https://www.pexels.com/photo/men-playing-wind-instruments-in-front-of-a-conductor-in-a-gray-suit-12847107/

Not so long ago I stumbled upon a problem that requires me to orchestrate a few blocs at once in a flutter. To my surprise, there isn’t any article about one! So I had to bootstrap ideas from bloc documentation and hack my way through it for my blocs to be able to talk to each other. This article is a hack that I found during my study.

In this article, I will assume that you understand how to create and use blocs. That’s why I won’t explain that. The complete solution is at the end of the article.

The Scenario

Let’s imagine we’re tasked to build an application mimicking a dam. The dam has one state: its waterLevel. The only interaction is we can increment its waterLevel. The app will tell us if the water is chill, beware, danger, or flooding.

To create such app, we can create a bloc for dam, then make 4 states that represents chill, beware, danger, or flood. BUT for the sake of this article, we’re creating two bloc: DamBloc and WarningBloc which depends on each other. The app will do two things:

  • When the water level rises (DamBloc), do some calculations to determine the warningLevel (WarningBloc)
  • When in FloodState (WarningBloc), after some seconds, reset the water level of the dam (DamBloc)

Now, let’s create some bloc orchestration!

Setup

Create a new flutter project named bloc_orchestrationand add bloc and flutter_blocto the dependency.

After that, create a /lib/blocs folder then fill it with 3 files:

// lib/blocs/dam.bloc.dart
import 'package:bloc/bloc.dart';

class DamBloc extends Bloc<DamEvent, DamState> {
DamBloc() : super(DamState(0)) {
on<IncrementWaterLevel>((event, emit) {
final waterLevel = state.waterLevel + 1;

emit(DamState(waterLevel));
});

on<ResetWaterLevel>((event, emit) {
emit(DamState(0));
});
}
}

abstract class DamEvent {}

class IncrementWaterLevel extends DamEvent {}

class ResetWaterLevel extends DamEvent {}

class DamState {
int waterLevel;

DamState(this.waterLevel);
}
// lib/blocs/warning.bloc.dart
import 'package:bloc/bloc.dart';
import 'package:bloc_orchestration/blocs/constant.dart';

class WarningBloc extends Bloc<WarningEvent, WarningState> {
WarningBloc() : super(ChillState()) {
on<UpdateWarning>((event, emit) {
if (event.warningLevel < chillThresshold) {
emit(ChillState());
} else if (event.warningLevel < bewareThreshold) {
emit(BewareState());
} else if (event.warningLevel < dangerousThreshold) {
emit(DangerousState());
} else {
emit(FloodState());
}
});
}
}

abstract class WarningEvent {}

class UpdateWarning extends WarningEvent {
int warningLevel;

UpdateWarning(this.warningLevel);
}

abstract class WarningState {}

class ChillState extends WarningState {}

class BewareState extends WarningState {}

class DangerousState extends WarningState {}

class FloodState extends WarningState {}
// lib/blocs/constant.dart
const chillThresshold = 4;
const bewareThreshold = 6;
const dangerousThreshold = 8;

And then, let’s flood the UI to use the dam.bloc.dart:

# lib/main.dart
import 'package:bloc_orchestration/blocs/dam.bloc.dart';
import 'package:bloc_orchestration/blocs/warning.bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'dam.screen.dart';

void main() {
runApp(const DamApp());
}

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

@override
Widget build(BuildContext context) {
return MaterialApp(
home: MultiBlocProvider(
providers: [
BlocProvider(create: (_) => DamBloc()),
BlocProvider(create: (_) => WarningBloc()),
],
child: const DamScreen(),
),
);
}
}
# lib/dam.screen.dart
import 'package:bloc_orchestration/blocs/dam.bloc.dart';
import 'package:bloc_orchestration/blocs/warning.bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

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

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: BlocBuilder<WarningBloc, WarningState>(
builder: (context, state) {
String text = "";
Color color = Colors.white;

if (state is ChillState) {
text = "We cool";
color = Colors.green;
}
if (state is BewareState) {
text = "Warning: possible flood";
color = Colors.yellow;
}
if (state is DangerousState) {
text = "Danger: high possibility of flood";
color = Colors.red;
}
if (state is FloodState) {
text = "It's flooding, pray to your Gods";
color = Colors.orange;
}
return Text(text, style: TextStyle(color: color));
},
),
),
body: Center(
child: BlocBuilder<DamBloc, DamState>(
builder: (context, state) => Text("${state.waterLevel}"),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
BlocProvider.of<DamBloc>(context).add(IncrementWaterLevel());
},
),
);
}
}

Now try running the application. This should be what you see:

That is pretty cool. Now, let’s make it so that the warning bloc changes when waterLevel changes!

MultiBlocListener

Create a new file called dam.notifier.dart in lib/blocs directory and add those codes:

import 'dart:async';

import 'package:bloc_orchestration/blocs/dam.bloc.dart';
import 'package:bloc_orchestration/blocs/warning.bloc.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class DamNotifier extends StatelessWidget { // 1
const DamNotifier({super.key, required this.child});

final Widget child;

@override
Widget build(BuildContext context) {
return MultiBlocListener( // 2
listeners: [
BlocListener<DamBloc, DamState>(
listener: (_, state) {
BlocProvider.of<WarningBloc>(context).add(
UpdateWarning(
(state.waterLevel / 2).floor(), // 3
),
);
},
),
BlocListener<WarningBloc, WarningState>(
listener: (_, state) {
// if it is flooding, some secs later, water level chills down
if (state is FloodState) {
Future.delayed(const Duration(seconds: 3), () {
BlocProvider.of<DamBloc>(context).add(ResetWaterLevel()); // 4
});
}
},
),
],
child: child,
);
}
}

Now I’ll explain some important concepts above:

  1. The orchestrator, or the DamNotifier itself is a stateless widget. It has to be a widget so that we can wrap our screen with the orchestrator.
  2. MultiBlocListener allows us to listen to multiple blocs at once, and play with its current state.
  3. We pretend the calculation of waterLevel to warningLevel to be waterLevel / 2. The advantage of this approach is that when we want to change calculation, we will only change the orchestrator, not the bloc itself.
  4. Let’s pretend that 3 seconds is enough to reset the dam back to zero (I know nothing about civil engineering ¯\_(ツ)_/¯).

After that, in your main.dart, change the child in MultiBlocProvider to DamNotifier:

MultiBlocProvider(
// ...
child: const DamNotifier(child: const DamScreen()),
),

Now, restart the app, and the orchestration should work!

For the complete solution: https://github.com/margunwa123/flutter-multi-bloc-orchestration .

--

--

Mario Gunawan
Flutter Tips

I'm a passionate mobile / web developer. Writing articles about software development, mainly about flutter and react.