A Framework for Flutter Part 2

Greg Perry
10 min readJan 21, 2022

--

A StatefulWidget’s Controller & State object

Let’s continue from the first article, A Framework for Flutter, and examine the mvc_pattern package by examining the two example apps that come with it. In truth, I would rather you develop your Flutter apps with the more comprehensive framework package, mvc_application. However, an understanding of how the mvc_pattern package works with Flutter to better access your State objects, for example, is a good reason to read on. After all, mvc_pattern is the parent class to the mvc_application package.

Let’s begin with screenshots of the two example app’s StatefulWidgets. Keep in mind, that they now have a ‘Controller’ component to store the ‘business rules’ and perform any necessary event handling. The first screenshot of the StatefulWidget, MyHomePage, is pretty uneventful. Its one lone instance variable, title, is set to final as it should be leaving just the createState() function to instantiate its accompanying State object. There’s no sign of its Controller.

On the contrary, the second screenshot of the StatefulWidget, Page1, has no qualms instantiating its Controller class (unimaginatively called Controller) right off the hop in its initializer list. Its instance variable, con, is set to final so that’s perfectly acceptable, but why is the controller object being used in the StatefulWidget?? That’s because its State class is private with a leading underscore. You see, a public API is being implemented in the StatefulWidget with the function onPressed(). It’s actually incrementing its counter in the StatefulWidget?! You’ll see why shortly.

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.

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

Let’s begin.

Other Stories by Greg Perry

The State Of Control

The first StatefulWidget, MyHomePage, continues to be pretty straightforward with its State object, _MyHomePageState. You’ll note I consistently keep the State classes private with the use of an underscore. It’s a good habit to get into so to keep that portion of your app out of reach of external agents. It’s important — it retains the state. Anything accessible is controlled using a predefined API (usual in the form of a series of public functions).

The examples’ State classes both extend the class, StateMVC. This, of course, extends Flutter’s own State class and is the primary component of this custom framework. For instance, it is this class that will incorporate the ‘State’ Controller as well as supply all the other functions and features mentioned in the first article.

In the second screenshot, you see its Controller is being instantiated and passed directly to the State object’s constructor. It’s then promptly cast to the instance variable, con. A State class, of course, does not impose the ‘immutable requirement’ and so mutable information can be freely manipulated here as well as in the Controller. Since that Controller was passed through the State object’s constructor, it is now is linked to that State object.

In the first screenshot below, since the StatefulWidget, Page1, had already instantiated its Controller object, that Controller is linked to its accompanying State object by calling the add() function in the State object’s initState() function. An instance variable also called, con, is then assigned a reference as well. Note, it’s ‘added’ before the required super method, super.initState(), is called. That’s done so the newly added Controller can then have its own initState() function called. Remember, whatever a State object can do, so can its accompanying Controller. You’ll find that fact to be very helpful in your Flutter apps.

Not Good Practice

Below are screenshots of the StatefulWidget, Page1, again. Now, I would not have implemented the way it is myself — with the State object containing and incrementing the counter. It‘s merely done this way to demonstrate how the Controller can now call its own setState() function — outside of the State object’s build() function making for a more responsive and effective Flutter app. The second screenshot conveys what would possibly be the implementation instead — with the Controller itself responsible for incrementing the counter. Regardless, let’s get back to the examples.

State-driven vs. Control-driven

Control The State

Let’s emphasize the fact the Controller now has the State object. This once out-of-reach yet crucial element of Flutter is now readily available to you and all it provides. Not only do you have its setState() function, of course, but you now have access to some of its other important properties. See below.

page_01.dart

Note the Controller’s property called, state. It is of the type, StateMVC, and references the State object the Controller was last linked to. It always refers to the State object the Controller is currently working with. It’s cast here to the type, _Page1State, so to increment the integer property, count, but why was the Controller’s ofState() function originally used instead in the first place? This will soon become apparent to you shortly.

The second StatefulWidget in the first example app is called, Page2, and it appears to be very much like the first StatefulWidget, Page1. In fact, it shares the very same Controller class called, Controller! This is where things get interesting. Note, the Controller is used to draw out the State object using its ofState() function again. However, this time it's referencing the State object, _Page2State. Makes sense — it's now working with the StatefulWidget, Page2.

page_02.dart

This custom framework allows this Controller to collect the State objects it’s being linked to over time, and that collection changes while going up and down the widget tree. For example, with the modifications to the code above, you can see below the Controller is linked not only to its current State object but remains linked to any of its previous State objects.

The State object for Page 1, for example, can still be accessed now using the function stateOf() which looks up State objects by their StatefulWidget! This is a powerful capability. You've access to all the State objects instantiated in this app so far. This will indeed prove very helpful in your Flutter apps. However, where is the integer property, count, for Page 2? — and what’s with this function, incrementCounter(), being used instead?

Access to _Page2State and _Page1State

Control Properties

While we’re here, let’s present some further properties that come about with the Controller object’s access to one or more of the App’s State objects. I suspect you’ll find a use for at least two or there if not all in your Flutter apps. For instance, every Controller object has a reference to the ‘first’ State object or the ‘root state’ of the app. A reference to that object comes from the property, rootState, and calling its setState() function may be a common occurrence in your apps. Next, there is the property, lastContext. As the name implies, it’s the latest BuildContext used in the latest build() function. How many times have you needed to use one of Flutter’s Widgets but it requires a BuildContext object? Normally, that would mean you could only use such a Widget in a build() function somewhere. Well, no more. Very powerful.

The next two are unique to this custom framework. The property, dataObject, will be discussed later on in the series while the property, inDebugger, would be a little self-evident. You can then determine in your code whether you’re running in production or not. A very useful indicator at times.

Control The Data

A quick peek at the Controller class again simply called, Controller, reveals it also extends a class found in the custom framework called, ControllerMVC. This parent class gives access to a State object's capabilities and more. However, in the case of Page 2, this quick look at the Controller class also reveals the count resides in yet another class object called, Model.

controller.dart

A Controlled Response

Granted, you’ve got to use your imagination here with such a simple example app. I mean, three separate classes are involved in incrementing one simple integer value — it’s a bit of overkill. However, your apps are not going to be this simple, and this demonstrates the benefits of breaking up responsibilities of more complex tasks into these categories: A Controller responds to a button tap, for example, and a Model is responsible for storing the data that’s then manipulated. (In this case, the Model does the manipulation itself.) It’s in this way, as you see in the gif file below, the count is retained even when the app retreats back to Page 1. The controller, Controller, remains in memory — as does the count in its Model object.

State of Control

The screenshot above and below continues to be that of the screen, Page 2. We’re looking at its State object now, and again, it’s very much like that for Page 1. I wrote a special class called, BuildPage, for all three of these screens to present the same ‘look and feel’, but it’s here where you see a reference to the previous screen, Page 1. I am literally instantiating the Page 1 StatefulWidget again and calling its public function, onPressed(). See below. Being such a ‘light’ class, calling a StatefulWidget (if written properly with only immutable content) is not much of a burden to resources. After all, the widget tree is being rebuilt again and again in a typical app, and so you’re then free to call its one API — the onPressed() function.

page_02.dart

Remember, instantiating a StatefulWidget in this fashion does not affect its accompanying State object either. That State object has already been instantiated and is doing its job retaining its state. This StatefulWidget isn’t even inserted in the Widget tree. This is just ‘a means to an end’ — so to call its onPressed() function. Note, it does assign the Controller class to its final property, con, but that’s perfectly fine. That class is not being instantiated again because it uses a factory constructor. You’ll find it’s best to have these State Controllers as single instances as they are to retain their state throughout the app’s runtime. There should just be one instance of them in most cases.

page_03.dart

In the last counter screen, Page 3, there are two buttons that increment the counters for the two previous screens. Each simply instantiates their respective StatefulWidget so to call their onPressed() functions. Perfectly acceptable. I mean, you can’t call their State objects. Those State classes were declared private as they should be. This is the cleanest way to go about it. The StatefulWidgets are self-contained, and you’re certainly not disrupting the state of things — only incrementing them.

page_03.dart

Note, there is a way to avoid instantiating a StatefulWidget again and again with that command, Page3().onPressed(). I mean, if it does bother your sensibilities to inefficiency. After all, you are creating a class again only to call its public function. Look below. Do you see what’s changed in the screenshot of the Page 3 State class? There’s the keyword, const, now that prefixes the calls to the two previous screens. In Flutter, you can make a class object a ‘compile-time constant.’ Doing so in this case, those classes are now not getting instantiated again and again— their onPressed() function is merely being called instead. I’ll show you what I did.

page_03.dart

That’s because the StatefulWidget classes now have ‘constant constructors.’ Each constructor has the keyword, const, as a prefix. However, this only works, because the Controller is no longer being instantiated in the constructors’ initializer list. Everything in a constant constructor must be immutable, and actively instantiating an object in the initializer list is far from being immutable.

page_03.dart

That means, of course, there would have to be another way of obtaining the State object for the public function, onPressed(). Have I mentioned the package, state_set? A really terrific little utility class. As you can see in the screenshot above, it’s just another means to retrieve the ever-important State object in Flutter.

Anyway, Part 3 of this series will continue with the package’s second example app. We’ll review the still more capabilities allotted to you if you use the custom framework, mvc_pattern.

Cheers.

Flutter Framework Part 3

→ Other Stories by Greg Perry

--

--