Bloc 8.0.0+ — Freezed BLoC Unions — Example
--
Read this if you are interested in optimising your code with pattern-matching.
It is effortless to ‘Freeze’ your bloc code to use unions and pattern-matching!
What Is Pattern-Matching?
I’m a massive supporter of the bloc library for Flutter apps, and this pattern shines brightest when used with the Freezed package for creating immutable data classes and sealed unions. Though we mention unions throughout the article, it’s more about pattern-matching. Pattern-matching is a syntax for implementing our code to an event handler. Here are examples of code with and without pattern-matching:
Learning Intentions
- You will learn about pattern-matching.
- You will learn to use the .maybeWhen method in your apps.
- You will see pattern-matching in a simple demo app.
Learning By Examples
In this article, we will be specifically looking at how code generated by the Freezed package offers us some interesting method calls that allow for some truly awesome custom widgets. These method calls are .when, .maybeWhen, .map and .maybeMap.
In this article, I will use pattern-matching with .maybeWhen to trigger a rebuild when emitting/yielding a new state. This capacity potentially means that every Stateless widget is interchangeably dynamic without using tedious if-statements. There is much more to this topic than what I will cover. I encourage you to dig deeper into unions. For reference, here is a description of .when and .maybeWhen calls in context to this article.
- .when — You must provide individual logic for each bloc state.
- .maybeWhen — You must provide logic for some bloc states and logic for defaults.
Here, I demonstrate the use of .maybeWhen. By .watch-ing the state, using a variable in the widget to monitor the States of our bloc. I use pattern-matching in this example to change the String value according to the BlocState (hereafter referred to as state). Pattern-matching happens due to the union formatting by the Freezed package, and the example uses the .maybeWhen method. The following code will help you better understand using the syntax of pattern-matching.
class DisplayText extends StatelessWidget {
const DisplayText();@override
Widget build(BuildContext context) {
final state = context.watch<GumballMachineBloc>().state;
return Text(
'${state.maybeWhen(
loaded: (counter) => 'Count: $counter',
hasQuarter: (counter) => 'Count: $counter',
noQuarter: (counter) => 'Count: $counter',
soldOut: () => 'Sold Out',
orElse: () => 0,
)}',
style: const TextStyle(
fontSize: 50,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
);
}
}
I used a custom Text widget to watch the State. I am using the state.maybeWhen method embedded inside a Text widget to make a so-called dynamic String. This way, when the hypothetical — loaded, hasQuarter or noQuarter — States change, the String will equal ‘Count: value’. Otherwise, when the State changes to soldOut, the String will correspond to ‘Sold Out’. All the other emitted States will result in the String value equal to zero. This is pattern-matching!
Using Pattern-Matching is Awesome
When designing the demo app for this article, my mind was racing because it made it much more manageable. After all, the boilerplate did not divert my attention! I needed to reign in several ideas to keep the code in this article simple. I also found that using pattern-matching allowed me to focus more on designing the UI and less on thinking about state management. It was the first time in a long time that I felt unshackled to create. I hope that while you are working through this demonstration, you will get excited and think of ways to use it! I am sure that you will notice too.
PART 1. FREEZED BLOC UNIONS
Setup
Watch this excellent YouTube video’s last four minutes for setup details. I wrote a section, which I removed because this video does it perfectly and cannot be made any more straightforward. The creator of that video is a gifted teacher.
Special Notes.
Note 1. I had to run this command several times when creating new Freezed files.
flutter pub run build_runner watch --delete-conflicting-outputs
There were times when I ran into a problem updating files. This problem seems to be a bug. Sometimes generating g.dart files doesn’t work correctly when running the code. To fix this, run the following instead:
flutter pub get
flutter pub upgrade --major-versions
flutter pub outdated
flutter pub run build_runner build --delete-conflicting-outputs
This should generate the g.dart files needed.
Note 2. Add this snippet to analysis_options.yaml to disable linter warnings.
analyzer:
exclude:
- '**/*.g.dart'
- '**/*.freezed.dart'
errors:
invalid_assignment: ignore
PART 2. INSPIRATION FOR YOUR CODE — HOW I USE PATTERN-MATCHING
Find the GitHub code here.
Introducing The Minimal Viable App
Behold the Virtual Bubblegum Machine! My demonstration uses pattern-matching in many dynamic custom widgets to drive this topic home. We will thoroughly discuss each of these later, but first, let’s look at the app in all its glory.
This nice and simple app has a beautiful image of a gumball machine, a gear that spins to get a gumball, and two buttons — ‘Pay’ and ‘Refill’. A display message shows the user how many gumballs remain in the virtual machine — in this example, it starts with three. The red button in the lower right-hand corner initially reads, ‘Pay’. Pressing this button turns the button’s colour green and changes the displayed word to ‘Paid’. Pressing the button allows the operation of the gear. The gear won’t spin if there is ‘no payment’. While in the ‘Paid’ state, pressing on the gear causes it to turn, decreasing the displayed number of gumballs in the machine. Doing this is repeatable until you get down to zero gumballs. At zero gumballs, paying and shifting the gear causes the display message to ‘Sold Out’ and another display to read, ‘Refund Given!!!’ and causes the ‘Pay’ button to become inoperable, turn grey, display the word, ‘Out’ and causes the ‘Refill’ button to turn green. The user can refill the gumball machine by pressing the corresponding button next to the ‘Pay’ button.
Rebuilding A Classic State Design Pattern With Freezed Bloc Unions And Pattern-Matching
I got a lot of the inspiration for this app idea from the chapter on the state design patterns in the book Head First Design Patterns Building Extensible & Maintainable Object-Oriented Software — 2nd Edition (Freeman et al., 2020). Understanding this design pattern will reveal the problem that using pattern-matching is trying to solve for this app. Please don’t confuse the word ‘pattern’ in pattern-matching and design patterns. This word being in both terms, is coincidental. For reference, please click on the following GitHub link, which contains a dart conversion of the state design pattern.
Purple App Diagrams
If a picture says a thousand words, then a purple app diagram will save you from looking at a thousand lines of code. This diagram graphically shows the widgets of the app, i.e.
- App Bar — App Bar widget — displays the title.
- Background Image — DecorationImage — bubblegum machine image.
- Display Label — Placeholder* — Texts or CircularProgressIndicator.
- Turn Gear — Placeholder* — Icon, Text or CircularProgressIndicator.
- FAB Buttons — Placeholder* — two FABs, colour and text changes.
*dynamic custom widgets that use Unions and pattern-matching.
‘Placeholders’ are widgets shown in the Purple App Diagram as layers. I named them because they contain a child variable whose concrete implementation CHANGES depending on the current BlocState. Each pattern match pairs with a single reference state. The layering represents the dynamic nature of these widgets. For example, the Display Label changes widgets when the bloc emits an initial or loading state; otherwise, it stays the same. i.e. the Display Label’s initial or loading reference States are a CircularProgressIndicator widget, while the other States might contain a Text widget. In this case, .maybeWhen calls, the orElse parameters are the default case while the non-orElse parameters are ‘state focused’.
I’ll show several purple app diagrams to help you easily understand the code.
The Full Purple App Diagram For The App
This diagram shows the complete widget profile for this app page. The crucial information in the diagrams is the widgets, bloc states and bloc events. The widgets’ names and bloc states are in the diagram.
Widgets
Two static widgets
- App Bar
- Background Image
Four Placeholder widgets
- Display Label
- Display Text
- Turn Gear
- FAB Buttons
BLoC States
The diagram shows the state profile. Their names are shown next to each of their widgets.
- initial
- loading
- loaded
- sold Out
- has Quarter
- no Quarter
- or Else (non-specified state)
BLoC Events
- start
- reset
- increment
- insertQuarter
- ejectQuarter
The UI Code
The BLoC Code
Take a look at how nice and minimal the BLoC code is:
Applying the Freezed formatting to the code is as easy as running this command in the console:
flutter pub run build_runner watch --delete-conflicting-outputs
Walking Through The Code with Purple App Diagrams
App Bar Widget
A static widget with Text and title.
Background Image Container Widget
A static widget with a decoration widget that contains an image.
Widget Holder
This widget has a column that holds the DisplayLable and TurnGear placeholder widgets.
Display Label Widget
Ultimately, the purpose of this widget is to show the number of gumballs stored in the virtual gumball machine. The widget displays a FlutterLogo in the initial state. While it’s loading, it presents a CircularProgressIndicator. Otherwise, the widget shows the number of gumballs.
Turn Gear Widget
I added this code to give the app some pizzazz! It contains a Placeholder and an Animated widget that allows the icon to spin after pressing it. Please don’t get hung up too much on the animated widget. It is in the code solely for your viewing pleasure! The most important bit is that the widget builds a CircularProgressIndicator in the initial state. While in the sold-out state, it rebuilds to a Text widget that reads, ‘Sold Out’. Otherwise, the widget builds the GearHandle widget.
FAB Buttons Widget
This widget contains a row with two floating action buttons inside it — I used the pattern-matching to hard-code several concrete custom button widgets to different states. We will discuss optimising the use of BlocEvents with these widgets: HasQuarterButton, SoldOutButton, DefaultButton, ActivatedRefillButton and DeactivatedRefillButton in better detail next.
Using BLoC Events
The biggest take-home message in the following code is that events are passed to the bloc using:
context.read<ObjectState>().add(BlocEvent.doSomething);
To make the code cleaner, I have chosen to pass callback methods — <void> hasQuarter, <void> noQuarter, <void> refill and <void> soldOut — to the ModifyButton widget with the appropriate BlocEvent.
That’s it! We’ve reviewed all of the code and many examples of pattern-matching.
Conclusion
You have just seen Freezed unions and pattern-matching. Perhaps you will agree; it eloquently levels up the code luxuriously.
- You learned to use the .maybeWhen method in your apps.
Hopefully, I have opened your eyes to the goodness of pattern-matching and inspired your imagination. I provided a simple case study example that heavily implements pattern-matching. Using unions and pattern-matching will improve your code quality and speed up the production of your apps. Happy coding!
Resources
GitHub Code
https://github.com/Goosebay111/fizzy_gumball_bloc_freezed_union_1.git
YouTube
You can watch an excellent YouTube video about this topic by clicking here.
References
1. Freeman, Eric, and Elisabeth Robson. Head First Design Patterns. O’Reilly Media, 2020.