Bloc 8.1.1+ —PART 1: Refactoring Hydrated Bloc Code For Functionality Gains With Code Road Construction

The Code Breaker
7 min readJan 11, 2023

--

Read this article to understand epic gains from enum to class refactoring while working with hydrated blocs and the steps involved.

After you read and understand the reasons for these steps, please read the following article in the series to see the subsequent actions that substantially clean and shorten the code using the Freezed package (found here). Please subscribe to get notified when this article releases.

Leverage Enums For Their Intended Purpose To Create A Better User Experience In Flutter.

I use Enums as often as I can in my code — since Flutter version 3.0 — because they are a simple, elegant, powerful tool. After including in the all too familiar components: Main, MyApp and MyAppPage, 9-out-of-10 times, I add Enums to define relationships between elements and to future-proof my code with this road construction technique.

Enums are elegant because they keep it simple. This simplicity leads to a limitation, which is rigid compile-time-only implementation which makes it impossible to change any of the values they contain.

Enums have the added benefit of working well with hydrated blocs, and as a perk, we are looking at refactoring code with them. Let’s start by considering this enum:

code for a simple enum

This enum has the values: magical and sharp. Each value holds a damage variable and a hidden name variable under the hood for free. We can cycle through the values using most dart iteration tools — i.e. .map. However, the inherent goodness that makes it a great tool requires limitations because of the compile-time setup.

It would be best if you did not modify the enum’s values because it would rob it of its simplicity by adding technical debt. After all, this is not how they are usually thought of, which can be confusing. Indeed, this is not within the conventional scope of an enum. There are better tools we should consider using to accommodate the other requirements better understood for the task — Classes.

Code Road Construction — Why Start Small?

In his book Clean Code, Uncle Bob reasons for the purposeful scaling up of code during software development in this way:

At first the roads are narrow and practically nonexistent, then they are paved, then widened over time… This growth is not without pain. How many times have you driven, bumper to bumper, through a road “improvement” project and asked yourself, “why didn’t they build it wide enough the first time!?”

But it couldn’t have happened any other way. Who could justify the expense of a six-lane highway through the middle of a small town that anticipates growth? Who would want such a road through their town?

— Martin, Robert C. Clean code: a handbook of agile software craftsmanship. Pearson Education, 2009. pages 157–158.

Ultimately, we want to keep our code to the minimum because, as Khorikov describes in his book, Unit Testing: Principles, Practices, and Patterns, code is a liability to a programmer. The reason why is complexity:

Code complexity is one of the biggest challenges you’ll face in software development. The more complex the code base becomes, the harder it is to work with, which, in turn, results in slowing down development speed and increasing the number of bugs … This brings an additional mental burden to the process of programming. Remove as much of that burden from yourself as possible.

— Khorikov, Vladimir. Unit Testing Principles, Practices, and Patterns. Simon and Schuster, 2020. Page 104.

So, I intentionally start by using enums as the “narrow road” to simplify data-driven programming in Flutter and only scaling up with “code road reconstruction” when necessary by keeping these steps in mind.

  1. Start code intentionally using enums for their intended purpose (fewest lines and easiest to understand).
  2. Then refactor the code to introduce complexity.

This way, we can change the code at any time. If we do not need to change it, then great. But, if we do, we have an easy-to-use process to follow with refactoring.

Process: Refactoring Enums to Classes with HydratedBloc

Our goal is to change the enum to a class so we can gain the ability to change the damage variable value, which we currently cannot do because of the enum’s limitation to have variables only set at compile time.

What You Will Learn

  • You will learn about refactoring Hydrated Bloc Code from using enums to classes.
  • You will see that the added functionality is viable by modifying variables.

Refactoring Hydrated Bloc Code From Enums to Classes To Gain Functionality

Disclaimer

I present this code in single blocks because it is more accessible to copy and paste. Scaling up has a tradeoff because we are deliberately adding complexity. This results in bigger blocks of code — up to three hundred lines in length. Please be assured that I have taken every opportunity to make the code as compact as possible without resorting to packages which could significantly reduce the code volume. In the wild, this code is in more readable chunks.

I promise to show how to refactor the code using a package to substantially clean and shorten the it. This article describes the reasoning behind the initial refactor, and the following article cuts out a lot of the boilerplate code and is found here.

Beginning code

Find the GitHub code here.

This example is a simplified version of code that I have already explained in another article found at this link.

Add these dependencies to your pubspec.yaml

flutter_bloc: ^8.1.1
equatable: ^2.0.5
hydrated_bloc: ^9.0.0
path_provider: ^2.0.11

This code is simple, but for what it does, it is powerful. However, you will notice that a large part of the code consists of the requirements for HydratedBloc. HydratedBloc requires to have toJson and fromJson, which has knock-on requirements, which means we need to add code in the SubjectBloc and SubjectEvent classes for the bloc to work correctly.

Beginning Code

One neat trick is using Enums to instantiate widgets from their values with a map. Besides going exceptionally well with the functional programming paradigm (because FP is all about iterables), using enums adds to their appeal because of how clean it makes the code for the widget.

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

@override
Widget build(BuildContext context) {
return Wrap(
children:
// here is where I use the map to use the enum's values to instantiate SubjectChip widgets.
Item.values.map((Item enumValue) {
return SubjectChip(item: enumValue);
}).toList());
}
}

Simple Steps For Converting Code

Now the secret is revealed. The true power of using enums is that you can follow a few steps to replace them with classes without much hassle. The refactoring that I use is a modification of a technique from the book, Five Lines of Code by Christian Clausen. (Clausen, Christian. Five Lines of Code. Manning Publication Company, 2021). The steps are not identical to those of the book, so I will detail them here:

STEP 1. Create an interface with a similar name to the enum — Item2 — with a constructor.

  • Add the variables that are the same as those on the enum, including the hidden name.
  • Add the toMap, fromMap, toJson, and fromJson methods to the interface to deal with the hydrated bloc storage.

STEP 2. Create a class for each enum value that implements the interface.

STEP 3. Because we are using HydratedBloc for its functionality, we use the Dart Data Class Generator extension to add the necessary methods:

Screenshot of Dart Data Class Generator Icon and details.
  • Add fromMap, toJson, fromJson, and copyWith methods to the interface, followed by the concrete classes.
  • IMPORTANTLY, you must override the == and hashCode methods to avoid inequality issues that arise with updating the HydratedBloc stored values because the storage uses the == and hashCode methods to determine if the state has changed.
  • Remove the variables that you wish to modify from the == function. In this case, I removed the damage value autogenerated by the Dart Data Class Generator extension. I did this because we will change this value later in the code, and problems will arise if it is there.

STEP 4. Comment out the enum and change the necessary types of the code from Item to Item2, or change the code to one of the appropriate classes that implement the interface.

  • The compiler errors will guide you to what needs changing.

STEP 5. Because the enum gives us free access to a list of its values, which generates every stored value, we must remove the enum and replace it with a new list of objects.

STEP 6. Optional. Refactor by renaming Item2 abstract class to Item. You can now eliminate the enum.

Find The GitHub code for this here.

Intermediary Commented Code

Completed code

Now that we have followed the steps, let’s test that it works.

Add the following code to the:

  • MyHomePage,
  • SubjectEvent,
  • and SubjectBloc classes.

Please press on the chips to add them to the total seen in CircleAvatar and press the FloatingActionButton to increase the values stored in the state. Restart the app to ensure the proper functionality of the HydratedBloc to save the state remains.

Find the GitHub code here.

Final Code

In conclusion, I have discussed my reasons for using enums in my code. When necessary, I can easily convert them to a class which gives me added functionality. I have shown the steps that I use to refactor this code. The refactoring has added functionality but has come at a cost — far more boilerplate code and violation of the DRY principle! We tackle this compromise to gain functionality in the following article (here). Happy Coding.

Next in the Series

Find out from here how to refactor the code with Freezed to make it much cleaner.

--

--