StateX — Story 1
Working with a Dart package usually involves downloading its very code so as to ‘host’ it on your machine — you, of course, then use it while developing your app. In most cases, all the packages are downloaded to the directory called, .pub-cache. For example, when you try out the state_extended package, it too will be found in that folder. Below is a screenshot of that folder on my machine.
Learn By Example
In this series, we’ll work with the example apps that accompany the StateX source code. It’ll be the fastest way to learn its functions and features. Like most packages, the example apps will be found in the package’s own folder under the directory called, example. See the screenshot below. I would suggest you create a new Flutter project somewhere, keep its default name to, example, and then port the contents of the package’s ‘example’ directory to that location for you to then try out.
Call Page 1 From Page 2
Start up the example app (as depicted in the gif file video running below), and we’ll get you introduced to its functions and features as soon as possible. Initially, one of four example apps that accompany the state_extended package is presented to you. This particular example app demonstrates the ready access you have to any particular State object in your app so as to execute its vital setState() function and rebuild and update the app’s interface accordingly — from *outside* its own build() function. Tap on the gif file to zoom in and get a closer look.
Repeatedly tapping on the ‘Page 1 Counter’ button on Page 2 will invoke the following command line:
final state = con.ofState<Page1State>()!;You can see this line in the first screenshot above. A property field named, con, is calling its ofState() function to retrieve the State object of a specified type that’s used on Page 1. Brilliant! You’re then free to increment that State object’s ‘counter’ field and reflect that change using its setState() function. Amazing!
At first glance, this all may appear pretty mundane, but to safely and reliably gain access to a State object from outside its residing Dart file is huge! By design, Flutter does not allow for ready-access to State objects as they’re critical to retaining state. After this series, you’re going to see how it’s done using the state_extended package and what that means in developing effective Flutter apps. For example, con.ofState<Page1State>(), implies the controller object, con, can retrieve any State object in the app if it’s a known and public class type (ie. no leading underscore). A powerful capability.
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. With that, you’ll find it best to read this article about mobile development on your computer 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.
Like the many other packages out there today, I wrote this class because I wanted ready access to any particular State object. I could then call its setState() function from outside of its build() function any time and anywhere I want. It was back in 2018, when I was first considering where the app’s event handling, its business rules, and its very logic should be found knowing the StatefulWidget class was out of the question.
You see, by design, a StatefulWidget is created, destroyed, and recreated again and again during the lifetime of a typical Flutter app. There should be no ‘mutable’ code in a StatefulWidget. At a minimum, all that should be in a StatefulWidget is its createState() function creating its State object. If there is any additional code, it’s crucial that it’s immutable code. With that, I concluded that the ‘business rules’ and event handling should reside in a separate ‘controller’ class.
This concept is not unknown to Flutter. I suspect you’ve already encountered a number of controllers working with the Flutter framework itself. They’re literally being passed to named parameters called ‘controller.’ One of the more prominent instances is when you use the TextField widget. It has a controller to supply the initial field value. Further, it handles all the events involved when editing the data in that field.
Another common place you would encounter a controller is when using the SingleChildScrollView widget. It is supplied with a controller to, again, address the series of events that occur during its typical operation. In this case, when scrolling its displayed data. It has proven to be very advantageous to introduce a controller to Flutter’s State objects in the same manner and for the same purpose. It makes building Flutter apps that much easier frankly.
Let’s continue and examine two more example apps that come with state_extended package. In truth, I’d rather you develop your Flutter apps with the more comprehensive framework package, fluttery_framework. However, an understanding of how the state_extended package works with Flutter supply yet further functionality to State objects is a good reason to read on. The fluttery_framework package uses the state_extended package at its core after all.
Let’s begin below with screenshots of the StatefulWidget from two of the example apps. Keep in mind there is now a ‘controller’ component for the event handling. The second screenshot of the StatefulWidget, HomePage, is pretty uneventful. As it should, its one lone instance variable, title, is set to ‘final’ leaving just the createState() function to instantiate its accompanying State object. There’s no sign of its controller. Not yet anyway.
On the contrary, the first 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 — the function onPressed(). A counter is actually being incremented in the StatefulWidget?! Now, isn’t that interesting?
The State Of Control
The second StatefulWidget, HomePage, continues to be pretty straightforward with its State class, _HomePageState. See below. You’ll note the State class is private with a leading underscore. It’s a good habit to get into. It keeps that portion of your app out of reach of external agents as it retains the app’s state. Anything that is accessible is controlled using a predefined API — usually a series of public functions. However, the first screenshot reveals a public StateX class. Not ideal, but if one has a good reason, it certainly can be public.
The examples’ State classes both extend the class, StateX. This, of course, extends Flutter’s State class. The StateX class will then incorporate the ‘State Object Controller’ (SOC for short). Doing so will give the controller access to the State object.
In the second screenshot above, 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 properties can be freely manipulated here as well. However, since that controller was passed through the State object’s constructor, it is now linked to that State object, and I would say is the best place for mutable code.
In the first screenshot above, 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 to the controller. Note, it’s ‘added’ before the required super method, super.initState(), is called. This is done in that order so the newly added controller can then have its own initState() function called. Remember, whatever functions a State object has, its accompanying controller(s) also have in most cases. We’ll discuss that more later in this series. You’ll find that fact to be very helpful in your Flutter apps.
State-driven vs. Contoller-driven
By the way, below are two variations of the StatefulWidget, Page1. They 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. This means you have options in implementation. Gotta love options.
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: mounted, widget and context. See below.
The Last State
Note the controller’s property called, state (first arrow above). It is of the type, StateX, and references the State object to which the controller was last linked. In other words, that property always refers to the State object the controller is currently working with. It’s cast in the screenshot above to the type, _Page1State, so to increment the integer property, count.
The controller’s ofState() and stateOf() functions were also demonstrated to you. However, they are traditionally used to retrieve instantiated State objects *outside* the current Dart file! For example, State objects further up the widget tree (currently running in memory) could be accessed if previously used in the app.
Control the Function
Again, in many cases, the best means to implement those functional requirements is to utilize ‘State Object Controllers’. As you see in the screenshot below, the AppStateX class can take in any number of controllers. Traditionally, it’s the controllers where the ‘brains of the operation’ reside. If there’s a great number of operations, they can be organized, and assigned to separate controllers for more efficient development and maintainability. Very nice.
The first example app has a second StatefulWidget, Page2, and it appears to be very much like the first StatefulWidget, Page1. In fact, it shares the very same controller class called, Controller. It’s presented in the screenshot below. Now, 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. That’s because it’s now working with the StatefulWidget, Page2. Follow me so far?
The state_extended package allows this controller to collect the State objects it gets linked to, and that collection changes while going up and down the widget tree (ie. going back and forth between screens). 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 using the function stateOf() which looks up State objects by their StatefulWidget, and since it’s a public State class, can also be accessed using the function ofState(). Again, this is a powerful capability. You’ve access to all the State objects instantiated in this app so far in a controlled and safe fashion. Huge!
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. You’ll find a use for at least two or three if not all in your Flutter apps. For instance, every controller object has a reference to the ‘first’ State object or ‘root’ State object of the app. A reference to that object comes from the property, rootState, and calling its setState() function may be a common requirement for you — particularly for smaller 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 a BuildContext object? Normally, they can only be found in a build() function. Well, no more. Simply find a controller.
The next two are unique to the state_extended package. The property, dataObject, will be discussed later on in the series while the property, inDebugger, should be a little self-evident. It’s used to determine whether you’re running in production or not. A very useful indicator to use.
Control The Data
A quick peek at the class simply called, Controller, reveals it extends the StateXController class. It also reveals the count resides in yet another class object called, Model.
A Stateful Event
The screenshot below continues to be that of the screen, Page 2. 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().
However, being such a ‘light’ class, calling a StatefulWidget (if written with only immutable content) is not much of a burden to resources, and so you’re then free to call its one API — the onPressed() function.
Remember, instantiating a StatefulWidget in this fashion does not affect its accompanying State object. That State object has already been instantiated and is doing its job retaining 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 (see below). You’ll find it’s best to have these State Controllers as single instances as they are to retain their own state of business logic throughout the app’s runtime.
On 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 as to call their onPressed() functions. Perfectly acceptable. The StatefulWidgets are self-contained, and you’re certainly not disrupting the state of things — only incrementing them. See below.
Lastly, let’s return to the pretty straightforward State class, _HomePageState., but note the contents on its initState() function. See below. We’ve seen the functions to retrieve a State object. However, there are functions to retrieve controllers as well. In the screenshot below, you’re able to retrieve any and all controllers that have been instantiated up to that point. Note, that if the specified controller has not yet been instantiated, a null value is returned.
Back To Zero
So the first three screens in the example app allowed you to increment the counters on the previous screens. However, there was also the added ability of ‘resetting the count’ on Page 1. I’ll now quickly explain how that was done. It was done using the fact that, in Flutter, although a State object traditionally stays in memory retaining its state, it will be disposed of and re-created when the StatefulWidget is assigned a ‘different’ key. Remember this.
I suspect in your current Flutter apps, you rarely concern yourself with the Key parameter in your Widgets. You either don’t pass anything at all, or it’s a constant Key value that you use to retrieve those widgets in tests:
await tester.tap(find.byKey(const Key('Page 3')));
The first State object in the example app, _MyAppState, is presented in the first screenshot below. Look at the value assigned to the Key parameter. It’s literally a ‘unique Key value’ coming from the UniqueKey() function! This means every time the _MyAppState’s build() function is called, the StatefulWidget, Page1, is assigned a new Key, and so its accompanying State object, _Page1State, will be disposed of and re-created.
Now, take a look at the second screenshot of the State class, _Page3State. When you press that ‘New Key for Page1’ button on this third screen, you’re instructing the ‘first’ State object to call its build() function again — of course by calling its setState() function. The StatefulWidget, Page1, is assigned a new key, meaning a new _Page1State State object is created, and thus, when a _Page1State State object is created, it always starts with a count of zero.
Keep It Simple & Keep It Flutter
Well, this is a good start to this series. Additional stories will continue from here examining those two example apps in more detail. Doing so will give you a full appreciation of what the state_extended can do for you. I myself don’t use the state_extend package — not directly anyway. Eventually, I developed a more comprehensive framework package called, fluttery_framework, which does everything I need to make a truly full-fledged production-ready cross-platform application. It accelerates the development of my Flutter apps using the state_extended package at its core.
It’s a Choice, Not a Chore
In conclusion, the StateX class doesn’t impose itself on you. It works with Flutter, and it works like Flutter. Its ‘State Object Controller’ can serve as a conduit from the conventionally private State object to the public StatefulWidget — never mind readily supplying the setState() function from outside a build() function.
Part 2 of this series will continue with the package’s capabilities to event handler triggered by the common events occurring during a mobile app’s typical life-cycle.