Lessons Learned Refactoring a Flutter App from Provider to Flutter Bloc

Why refactor? Why choose Flutter Bloc? The story answering these questions could be categorized as a coding horror story, but the lessons learned were plentiful.

Enter: Frankenstein

I began the practical learning of Flutter app development by looking to create a suite of simple reminder apps. Being my first mobile apps, despite over ten years of web development, and despite some very in-depth mobile app training sources, the first app in the suite became a Frankenstein. 😨 Kinda shocked it even worked at all, the result was a mishmash between Provider, GetIt, Observable-ish, and a small Rx utility extracted from a popular MIT-licensed library.

And because, at the time, I had a decent idea of where all the duct tape was at, (or so I thought), I then created two more apps in the suite based on that initial pilot. 😱

Fast-forward a year and a half . . .

After working day and night at my first Flutter Developer position, I was finally able to get back to those first three apps, and, oh my…!! I discovered the changes in the utility from the open-sourced library alone appeared insurmountable, let alone every other library and SDK that had updated in that year and a half.

D’oh!

Enter: Flutter Bloc

Fortunately, having been learning and becoming quite familiar with Flutter Bloc on the job the previous year and a half, the solution was obvious! — Refactor all three apps to Flutter Bloc! Brilliant!

In theory, it was thought this should be a fairly straightforward refactor. In using Provider, ViewModels were used, which roughly translated to MVVM. The Flutter Bloc approach, using the repository pattern, could also be considered MVVM, but where the blocs represent the ViewModels. Straightforward? Sure — “in theory.”

Thus, the refactoring began.

Retrospective: A Clear Flow

When the refactor was complete and I was able to reflect on it as a whole, the biggest revelation I found was in how much Flutter Bloc helped with data focus, and in getting your data flows into the lanes they were meant to be in.

The division of labor is quite distinct within the bloc flow: the UI reacts to state in your blocs, which read your models from streams in your repositories. Using Provider, on the other hand, allowed ChangeNotifier models to morph into doubling as services, which in the end became god classes, and that “theory” about this being a straightforward refactor went straight out the window.

Simply put, Flutter Bloc and the repository pattern helps keep your model data, states, and repositories separate, and those separations of responsibilities force you (for the most part) to really think about where things reside. Need a SnackBar? Setup a BlocListener. 👍

More Highlights

  • PopScope: When replacing WillPopScope (deprecated), don’t use canPop. Set canPop to false and use the onPopInvoke instead. Source: Flutter.dev
  • After setting up tests for a handful of files with Mockito, I found that I personally prefer Mocktail. There were a lot of things I wanted to get to know about Mockito (e.g. spies), but I could not keep up with the times the builder had to be run. At least not during a refactor such as this was. I don’t recall such headaches in setting up mocks with Mocktail — you set up some mocks, and they’re implemented by the Mocktail package when testing. Easy peasy. In the end I ended up stripping out Mockito, and have begun the process of adding in Mocktail.

Shortcuts

Admittedly, I did take some shortcuts. One in particular was with calls to the methods brought over from the ViewModels and placed in blocs. Those calls ended up being made directly — as you would call a cubit — bypassing a lot of bloc events. This was primarily due to the volatility and uncertainty of where everything would eventually reside. Fortunately, these eventual refactors to bloc events should actually be pretty straightforward and aren’t mission critical.

A Couple Firsts

  • During the refactor I created my first typedef, and subsequently learned a typedef doesn’t have to be just a function definition. It can actually be any type definition. A typedef for response objects got used often.
  • I was also able to capitalize on Dart’s new record type by splitting a common API return response. 🎉 A lot of records could lead to some potential readability issues, but they should be good if kept minimal.

Flutter is Fun!

Would I do it again? But of course! The second app’s refactor has already begun! I’m hoping to have it up and running within a couple months.

– Keith | https://keithdc.com

References

KD-reCall Logo
KD-reCall Logo
KD-reCall: Hungry on Hand | Reminder App


Posted

in

by

Comments

Leave a Reply