The BLoC paradigm has been the go-to solution for stream-based state management, with a plethora of mechanisms which abstract away the boilerplate of stream-based state management. But as we know, the more we understand the underlying mechanisms of our designs, the more meaningful our abstractions can be. In this post I will give one example of a parametric BLoC, which will help us centralize the business logic surrounding the download, upload, and validation of data. Hopefully this example will illuminate the mechanisms which allow us to do such type-based wizardry.
The creation of abstractions is at the core of programming. These abstractions become our concepts, the objects we manipulate across our codebase, and even their own manipulations. The better the abstractions, the more elegant the solutions, and the less code we need overall. And as we know, less code means less bugs, less development time, and less maintenance costs.
Our example situation
Let’s suppose we are working on an application for an animal shelter, which should manage the shelter’s animal registry. A user should be able to register animals to be adopted. Each different species has different attributes which are relevant, for example a snake’s length, a dog’s favorite food, or a cat’s number of legs. So, as expected, each different species will have a different form to fill, with different data.
So our design team comes to the conclusion that there should be a page tailored for each species that can be registered, with the possibility of new species being added in the future. These pages will be used to register new animals, and also to update already existing animals’ information.
The BLoC abstractions
So, some abstractions are pretty easy to spot. We will probably need a class for each animal species. We will also need a class for each species’ page’s BLoC. Maybe, if you’re cheeky, you have already spotted the possibility of an abstract class Animal. But the abstract BLoC will abstract away a core functionality of our application. The downloading, modification, and uploading of arbitrary types. Well, not arbitrary. Of species. So let’s get coding.
First the abstract class Animal
We added a simple getter isValid to make sure we never try to upload a invalid animal to the database.
And now let’s check out our dog class.
As we can see, dogs have a height, a weight, and a number of tails. We set conditions on what constitutes a valid upload. For now, registration of mutant animals will not be done through the app, because special considerations need to be taken in such cases.
And now that we have all of our models in place, comes the time of the abstract BLoC.
The parametric BLoC
So let’s think about this a little bit. Our BLoC will have to be able to download an animal from a database, upload it to a database, and change the animal it has saved. That translates to three events.
And our BLoC state should have an animal
For now everything seems pretty typical, other than the type parameters, which are kind of off-putting, but later we’ll understand the role they play.
So now yes, here comes the time we have been waiting for. The actual parametric BLoC. First I’ll show you the code, then we can walk through it together.
Yep. That’s it. As you can see, we have three event handlers, one for each event we defined previously. One more thing to note are the typedefs at the bottom, they are just for more comfortable function types. _downlaoder and _uploader will be the repository functions to handle the data, and in the BLoC pattern they are part of the repository layer, so they are beyond our scope.
Now let’s look at how we could use this to implement the BLoC for registering a dog. First our page will have input fields for the dog's height, weight, and number of tails, so when those change we will let the BLoC know with these events
The DogBloc inheriting the AnimalBloc
And now we can build the DogBloc event handlers using the AnimalBloc events, and we can also create new handlers without littering the AnimalBloc with dog-specific events, and emit new AnimalState<Dog> which will blissfully interact with the AnimalBloc logic.
As you can see, the DogBloc is fully implemented and ready to be plugged to the UI with very little code, and absolutely no uploading nor downloading logic. That is all handled either in the uploader or downloader functions for Dog specific code, or on the AnimalBloc for animal generic specifications.
Well, that was an abstract parametric BLoC. It might look like a lot of code, but the strength of this method and example is the work that’s to come when adding new species and new features. Adding a new species handling BLoC will be as simple as defining its class extending Animal, and making the needed animal manipulation events. But wait, there’s more…
The last wisp of magic I have to share today is something that could only happen after all of this parametric work has been done. A reusable parametric widget.
Any widget can be wrapped with this one and function as the upload animal button, for any animal. This way the actual BLoC functionality is in one place, while different UI widgets can be used for different animals.
I really do hope that this post has shed a light on the power of type parameters not only in Dart and Flutter but in all typed languages.
I’d love to hear your ideas on different responsibilities which can be abstracted away with a nice abstract BLoC. Maybe a REST BLoC whose type parametrization extends a Jsonable mixin? Blow me away with your ingeniousness, I’m sure you’ll have no problems with that.