The Wonderous App Wanders
It needs more control…I mean controllers
Some days ago, I published an article offering an alternative in the arrangement of the source code for the recently released Wonderous App. Its code was copied to a separate repository and further arranged and organized in such a way as to encourage better maintainability and scalability. That ‘member-only’ article is again available to you here.
At the very end of that article, I mentioned the Fluttery Framework was used in this version of the Wonderous app and gave a quick reason as to why I chose to use it in that exercise. It was there I also commented on how the app’s code concerning its ‘business logic’ tends to wander a bit — some being performed in code considered to be on the ‘interface side’ of the app. A bad case of ‘high coupling, low cohesion’ is the typical software engineering jargon.
I was implying the Fluttery Framework was able to address this as well. I use it in all my apps to make my Life as a Flutter developer that much easier. It introduces a SOC or a ‘State Object Controller’ to any and all the State classes that may be used in my projects, and I find it a very useful framework indeed. As it is, I did write this framework.
I Like Screenshots. Tap Caption 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 their captions to see the code in a gist or in Github. Tap or click on the screenshots themselves to zoom in on them for a closer look. If reading on your phone, you’ll have to then use your two fingers to expand and collapse the image. Ironically, you may find it best to read this article about mobile development on your computer rather than on your phone.
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 or Facebook. They may come out as static pictures or simply blank placeholders. Please, be aware of this and maybe read this article on medium.com or on their own app.
Let’s begin.
State The Obvious
In this article, I will again unabashedly promote the use of this framework as it’s designed to ‘Keep it Flutter’ during development. Unlike certain others out there frankly, this framework’s implementation and how it’s used will be readily recognizable to any developer who’s at all familiar with Flutter. It’s a framework that provides what I saw was lacking in the other frameworks or architectures. For example, error handling is emphasized, as well as a means to present either the Cupertino interface or the Material interface at runtime is readily available to you. Another characteristic seen lacking was a design pattern. In this case, one that acknowledges the three aspects present in nearly every computer program: an app’s interface, an app’s data, and an app’s business logic or event handling. That alone makes for more proficient software development and maintainability.
I make no qualms about it, it’s the best framework out there, and I insist you all use it! Now, with that unrealistic demand out of the way, let’s continue in this article to further explain why the idea of a ‘State Object Controller’ will prove to be indispensable to you and your future efforts in Flutter. This article will demonstrate this using one particular circumstance found in the original Wonderous app. Please, read on.
A Change in Locale
The app has a modest Localization feature allowing you to switch between English and simplified Chinese. The little tab initially labeled English is represented by the StatelessWidget, LocaleSwitcher. The get_it package is used ‘to watch’ the app’s Locale for any changes and calls the StatelessWidget’s build() function (rebuilding that widget again) if and when the current Locale is changed. See the first screenshot below.
When you tap on that tab, the function, handleSwapLocale(), is called (see second screenshot above) and, in turn, calls the following function in the ‘Settings Logic’ class to indeed change the app’s current Locale:
await settingsLogic.changeLocale(newLocale);
Not As Intended
Contrary to its intended design, the state of a StatelessWidget is changed. Again, this is possible with the get_it package. However, it’s certainly not ‘keeping it Flutter’ when you override a StatelessWidget’s element class and call its function,markNeedsBuild()
, to notify Flutter there’s a change in its state. The Flutter documentation makes no mention of this approach, and I have taken the position that’s because it’s not an endorsed approach. The documentation keeps it to the setState() function when it comes to updating an app’s interface after a change — It’s the setState() function after all that eventually calls the markNeedsBuild() function; not you.
No, changing ‘the state’ or the value of a widget during an app’s lifetime means a StatefulWidget is to be used and not a StatelessWidget. Besides, doing so, you can then take advantage of a SOC (State Object Controller) when using the Fluttery Framework. In the first screenshot below, the StatelessWidget is now a StatefulWidget in my version of the Wonderous app. It uses the class, StateX, that comes from the Fluttery Framework to ‘extend’ Flutter’s original State class — hence the ‘X.’
Where’s My Soc?
The second screenshot above is the same screenshot as the first, but instead highlights the State Object Controller, SettingsLogic. It’s being instantiated and passed to the super constructor. That controller is then retrieved in that class’s own constructor and assigned to the variable, con. This allows the function, onPressed(), found in that controller to be called whenever that tab is tapped on.
Do you see the differences between the two screenshots above? See the clear separation of responsibilities between the original LocalSwitcher class and the new LocalSwitcher class in the second screenshot above? Note, looking that the new class’ stretch of code you’ve no idea what really happens in the onPressed() function (onPressed: con.onPressed
), and that’s a good thing. Doing so allows for easier maintainability and scalability. It’s more modular. For example, ten more languages can be implemented tomorrow and not one single character needs to be changed here in that State class to do so. A very powerful and desirable capability when it comes to writing software.
Further, note how the API between the app’s ‘business rules’ (logic) and the app’s interface tends to match the name of the parameter receiving the function? (The function, con.onPressed()
, is passed to the named parameter, onPressed.) It’s a consistent approach following the, ‘Keeping it Flutter’, methodology. The ‘interface programmer’ will know the name of the function used when that tab is pressed because the ‘business rules programmer’ will have named it, onPressed. See what I mean? Such a consistent API approach is excellent when a team is developing the software.
Control The Pattern
By the way, in keeping with the importance of consistency, that’s the general pattern you’ll see in the Fluttery Framework when utilizing such Controllers in State classes:
The controller is passed to the State’s super constructor, it’s then assigned to a variable (usually named ‘con’) either in the class's constructor or in its initState() function. That ‘con’ variable is then referenced here and there in the class’s build() function or any other function defined in that class. The ‘con’ variable represents the logic/event handling.
It’s the Controller that contains the business logic and performs the event handling while the State object is just concerned with the interface. See what I mean? Granted, there are variations in how and when the Controller is assigned to a variable, but that’s the general pattern.
A Single Logic
Let’s now take a look at the class, SettingLogic. It appears in both screenshots below. In the first screenshot, you can see it extends the StatXController class making it a State Object Controller. Further, note this class has a factory constructor. I’ve found, in most cases, it’s advantageous to use a factory constructor in my SOC’s. After all, you want your app’s logic to remain in memory — with only one instance of the Controller class.
In Sync Without Knowing It
In the second screenshot, the last function listed is the onPressed() function. Note, with such a separation of responsibilities, you don’t even know from the interface side whether the onPressed() function performs an asynchronous operation or a synchronous operation. Again, that’s a good thing. Nor, should you know on the interface side. This allows the logic to be readily adaptive and change, for example, the operation to a synchronous operation at any time without consequence. Powerful stuff.
The Single Factor
Being a class with a factory constructor, you know there is only one instance of this Controller, SettingsLogic, ever used in the app. That gives you those variations I mentioned earlier as to how a SOC is implemented in any given StateX object. For example, look at the first screenshot below. The control is seemingly instantiated again in the StateX object’s initState() function, but with a factory constructor, it’s not instantiated again. Instead, the original instance is assigned to that memory variable. That variable is then referenced throughout the remaining functions in that class as many times as necessary. By the way, placing such a reference in resident memory allows for a little more optimized performance as well.
Note, in the second screenshot above, the controller is seemingly instantiated every time the tab button is tapped. Again, it’s not. Because there’s just one instance involved, and it’s only referenced again in just one other specific event (if and when the tab is pressed), this is another approach to consider because a factory constructor is used. As a rule, all my controllers follow the Singleton pattern and use a factory constructor.
Control The State Of Events
Passing a StateXController object to a State object’s super constructor (first screenshot below) gives the controller access to all the functions and features in that State object. Now, anything a State object can do, the controller can do as well. Very nice.
Thus, when your controller is ‘registered’ with a State object and its State object’s initState() function is called, its own initState() function will also be called. Anything your controller may need to be initialized, for example, before that widget is displayed can now be done so in its own initState() function. Very nice indeed.
In the screenshot below, the controller, SettingsLogic, has two ‘State event’ functions already implemented: initState() and dispose(). Both are fired by the State object, _LocalSwitcherState .
The StateX class object’s corresponding initState() and dispose() functions will call in turn any and all controllers (yes, there can be more than one) that were ‘added’ to them at any one time. See the screenshot below.
Get A Handle On It
Another 15 other event handlers are available when your controller
is registered to the StateX object. That’s because it’s added as an observer (see below) making it ‘aware’ of still more system events that may occur on your host device. For example, when the Locale on the mobile phone changes.
WidgetBinding.instance.addObsever(this);
The screenshot below is a summary of those event functions. Imagine the possibilities in your own flutter apps:
I will leave it to you to examine the code yourself and see what I mean when I say, you’ll never go out developing your Flutter apps without your SOC’s again.
I know — I cringed typing it.
Cheers.
By the by, below is a free article further describing the StateX class used in the Fluttery framework.