Conceptual discussion in what to look for in a State Management solution.
Indeed, when I switched over to Flutter two years ago, I wasn’t thrilled with the ‘State Management’ solutions offered at the time. When it came down to it, all I wanted was a place to put my ‘mutable code’ (my app’s business logic) without the compiler complaining about it. You see, placing such code in a StatefulWidget or a StatelessWidget is discouraged — only immutable code should be in those objects to optimize Flutter’s performance and take advantage of its declarative programming characteristics. If you don’t, you’ll be greeted with the following warning all the time:
“ There is no imperative changing of the UI itself (like
widget.setText)—you change the state, and the UI rebuilds from scratch.”
— flutter.dev Start thinking declaratively
When first looking at the solutions offered, for example, I was surprised when I saw Redux in the mix. I mean Redux, with its many many ‘actions’ and ‘reducers’ make up what’s essentially a large and elaborate messaging system. It was designed by Facebook to ‘manage’ over time the many developers (with their high turn-over) who would be working on the huge website that is facebook.com. It was designed to no doubt satisfy managers — certainly not to help developers write efficient code. Imposing such an approach on a Flutter app with the sheer size and complexity of its boilerplate?? For a phone app?? It’s my opinion Redux has no business in Flutter. I turned down a contract once because the client’s Flutter app was in Redux. Redux!? No.
With that, however, I’m not going to sponsor any particular design pattern outright in this article. Nope, in this article, I‘ll get down to two particular traits; two specific characteristics I feel are desirable in any design pattern you may pick for Flutter.
I Like Screenshots. Click For Gists.
As always, I prefer using screenshots in my articles over gists to show concepts rather than just show code. I find them easier to work with frankly. However, you can click or tap on these screenshots to see the code in a gist or in Github. Further, it’s better to read this article about mobile development on your computer than on your phone. Besides, we program mostly on our computers — not on our phones. Not yet anyway.
No Moving Pictures, No Social Media
There will be gif files in this article demonstrating aspects of the topic at hand. However, it’s said viewing such gif files is not possible when reading this article on platforms like Instagram, Facebook, etc. They may come out as static pictures or simply blank placeholder boxes. Please, be aware of this and maybe read this article on medium.com
Know Your Place
As I’ve stated, when I began in Flutter, I needed a place to put the business logic for my apps. More often than not, such code deals with mutable data that needs to be retained throughout the life of the app. Of course, with Flutter and its declarative nature, where would be a good place for such code?
Sure, all your ‘mutable’ code could go into the class that makes up your State object, or it could go into other classes in the very same Dart library file. That’s a good place since you’d want ready access to the State object anyway —the one reason being it’s the State object’s setState() function that refreshes and displays the ‘current state’ of your Flutter app to the user. However, putting it all in one file will, at times, make for a rather big and messy State class in a large and unmanageable Dart file. Instead, placing such code in separate Dart files would be a better solution.
When I switched over to Flutter two years ago, I wanted a separate class that represented the ‘Business Logic Component’ of the app but still had ready access to the rest of the app. Most importantly, I wanted a separate class that still had the means to convey the app’s ‘current state’ if and when something changes. In other words, I wanted a separate class that allowed for mutable data yet have all the functionality of a State object. Now, how would that look like?
“Ephemeral state (sometimes called UI state or local state) is the state you can neatly contain in a single widget.
Other parts of the widget tree seldom need to access this kind of state.
In other words, there is no need to use state management techniques (ScopedModel, Redux, etc.) on this kind of state. All you need is a StatefulWidget.”
— flutter.dev Ephemeral state
I would have ended the quote above differently to be just a little more explicit: “All you need is a StatefulWidget with its State object retaining that local state.”
Further, it need not only pertain to widgets. The first sentence could have just as easily ended as “…you can neatly contain in a single class.”
The Business End of Your First Flutter App
As in many of my articles, I’ll turn to a well-known Flutter example to get my message across. Let’s go back to the good old Write your first Flutter app example. Below is a snapshot of that example but written with a Bloc class containing the app’s business logic — what little there is in such a simple example. Note, the points of interest highlighted by little red arrows.
See The Abstract
You can see the Bloc object referenced here and there in the build() function providing the data and the event handling related to the data. Now, how is that ‘Business Logic Component’ obtaining ready access to the rest of the app? More specifically, obtaining ready access to the State object, _RandomWordsAndroid, in this example? Well, you can see, _RandomWordsAndroid, is a subclass of the class, _RandomWordsState. Below is a full screenshot of that class.
It’s easy enough to see that, when the Bloc is instantiated, it’s merely passed an instance of the State object, and there you have it — your business logic now has access to the State object. Those observant readers will also note the leading underscore. Hence, the class still remains in the same Dart file. Again, larger more complex apps would likely warrant the Bloc code in a separate Dart file, but since this is such a simple example, please forgive my discretion for now.
Please, also don’t think I’m involving the BLoC design pattern here when I use of term ‘Bloc’. The term is used here to describes how to represent the ‘logic’ or, dare I say it, the ‘state’ of this part of the app separate from other aspects of the app. Although such a separation is indeed found in the BLoC design pattern, the term ‘Business Logic Component’ stands on its own. IBM, for example, was using the term when e-commerce was in its infancy.
The Data From The Interface
One common trait you’ll find in a desirable design pattern is the separation of the app’s interface from everything else. Further, there should a degree of abstraction involved in this separation. And so, in this simple example, ‘how the data is displayed’ is separated from ‘what data is displayed.’ Further, you’re likely very familiar with the Write your first Flutter app example, and yet looking at the stretch of code below, you’ve no hint that the app is, in fact, listing Word pairs with trailing little hearts. That fact is concealed with a degree of abstraction.
Separate the app’s interface from everything else.
There should a degree of abstraction in this separation.
Shows the How Not The What
What you do see is that whatever is being listed is being separated by a Divider widget and is displayed rather large with a font size of 25, but that’s about it. However, that’s a characteristic we’re looking for in a design pattern. For example, when it comes to the interface aspect of this app, that’s enough information to know. The common traits I’m talking about, today, involves the separation of responsibilities. It also involves a ‘degree of abstraction’ in how the separation is performed— such an approach will make for better development, better maintainability, and a better app. I’ll explain further.
Note, the property names of the Bloc object in the ListTile widget. Now, those names weren’t arbitrarily picked out of a hat. The last two highlighted with arrows, for example, were named after the named-parameters in the ListTile widget while the property, bloc.data, is named after the positional parameter found in the Text widget. Frankly, the function, bloc.build(), could have been named, bloc.builder(), to follow that trail of thought, but the word, build, is versatile enough to then work with other widgets, no?
Below is a screenshot of the Bloc highlighting its API used by the interface. The interface doesn’t know what it’s listing, (i.e. low coupling) but it does know what API to use to list it. The screenshot reveals the true ‘state’ of this part of the app — it reveals what sort of data is actually being listed. The degree of abstraction used by the Bloc class with its ‘generic names’ for specific getters and functions is a conscious effort to put in place a consistent application programming interface (API). This allows for possibly other interfaces in the future to readily work with this Business Logic Component. The Flutter framework’s very own API (the names of its parameters and functions) is used as a guide.
Access The State
So, as it happens in this particular example, the Bloc itself (the app’s logic) now ‘retains the state’ for this aspect of the app. When instantiated, the Bloc took in as a parameter, a State object. The reference to this State object primarily serves to call again and again the declarative code found in that State object’s build() function — so to convey the ‘current state’ of that State object at a given point in time. To call that declarative code, of course, the logic merely has to call the State object’s setState() function.
There’s an example of calling the setState() function in the screenshot below. It shows what happens after the Bloc object responds to a ‘tap’ event. The user taps on one of the little heart icons saving the selected WordPair, and the Bloc’s onTap() function performs the operation. Of course, the interface doesn’t know that. Note, the class, _DataBloc, extends the class, Bloc. We’ll take a closer look at that class next.
Be The State
When dealing with the Bloc, because it has access to the State object when first instantiated, you can use this Bloc as you would a State object. You certainly have access to the ever-important setState() function. In fact, there are four functions that essentially perform the same thing. They merely vary in name depending upon personal inclination frankly. This is another example of a degree of abstraction implemented in this approach. It’s an attempt to make the Bloc class the solution to all possible circumstances and preferences.
Because the Bloc object has reference to the State object, it has further access to the State object’s own properties: widget, mounted, and context. Imagine what you can do with those properties in your separate logic?
The State’s Logic
Again, the more observant readers will have noticed this version of the example app is using a subclass of Flutter’s State<StatefulWidget> class. I’ve called it, StateBloc, in recognition that it works with the class, Bloc, to supply a State object to a business logic component.
This StateBloc class allows the developer to access the State object’s StatefulWidget using the property, widget, as well as the setState() function of course. However, more specifically, it allows its Bloc counterpart to gain access to it.
Don’t Block This Class
Below is a screenshot of the whole Dart file that contains the classes, Bloc and StateBloc. Now I’m not advocating you use these two particular classes in your projects. I’m certainly not supporting this Dart file. I just whipped it up to demonstrate the topic at hand. I’ve got my own framework package for my apps — it has the characteristics described here, of course, and more. Yeah, I meant it when I said I wasn’t thrilled with the ‘State Management’ solutions offered two years ago. So much so, I wrote another one to throw in the mix.
No, if anything, I merely invite you to take this ‘Bloc’ class and see how it works. You see, such a Bloc/Controller isn’t any good if it can’t work with other parts of your app — in other parts maintaining their own state/data for instance. You see, the Bloc class below allows for a Bloc object to work with more than one State object as your app goes down its widget tree. In other words, you can add the same Bloc object to more than one State object.
Of course, your app could also have more than one separate and distinct business logic component altogether. For example, one could be for just the app’s ‘home screen’ and all the logic that entails while another one could be concerned with the general ‘look and feel’ of the app as the whole.
Pick Your Platform
As an example, you’ll notice in the revised version of Write Your First Flutter App, there’s a double-arrow icon up on the navigation bar in the left-hand corner. Clicking on that icon allows the user to switch back and forth between the Material and the Cupertino interface design — between Android’s offered interface and iOS’s recognized interface.
A separate State object is involved in this particular operation. Further, by design, there’s a separate and distinct Bloc that helps the user with accessing that State object — as well as, more importantly, retaining which interface design is currently being used. Note, the Bloc is named _AppEventHandler, and it too displays the same two characteristics highlighted in this article: A separation of responsibility with a degree of abstraction in its API.
Isn’t Flutter great? Not only can you write one app for both the Android and iOS platforms (as well as the desktop and the Web for that matter). You can also supply the user the option to switch between all the interface designs currently offered with a tap of the screen. All you need to do is know how to work with the app’s many states. Now, how does the interface switching work, you may ask. Well, that’s for a future article.