Mixin’ State Objects Part 2
Access Your App’s State Objects. Anywhere. Easily.
The previous article, Mixin’ State Objects Part 1, is displayed in the screenshot below, and I’ll suggest you read that article first before you read this one. This article is a continuation of the first and will walk through the very mixin introduced in the first article called, StateSet. Admittedly, the first article was published some five months ago at this point, and the mixin has since evolved, and so additional enhancements will certainly be reviewed here.
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
Indeed, things have evolved, and the mixin even has a new example app. This example, however, continues to use three StatefulWidgets now named Page1, Page2, and Page3 — each calling the other one after the other. Regardless, in the screenshot of the example app below, you’ll see something a little out of the ordinary highlighted with a little red arrow. The code displayed below is found in the State object for the second screen (having come from the first screen) and although the first StatefulWidget, Page1, has already been instantiated and displayed by this point, it’s being instantiated again. However, only so to call its function, onPressed().
Certainly, calling a public function defined in a StatefulWidget is not a common practice. In fact, in Flutter, it’s encouraged that StatefulWidget remains rather ‘light’ in its content storing no mutable properties and essentially only creating its accompanying State object. That’s because, as you’ve likely found when learning Flutter, it’s the State object that carries much of what is to be of your Flutter app. Therefore, note this uncommon practice is performed here merely to keep the example code simple and to point more emphasis on the topic at hand. I will say, however, that such a practice when done properly does have its place — I’ll explain that later.
As in the first article, let’s step back to the beginning of this example app and review the first screen (the StatefulWidget called Page1) in the screenshot below. There’s a video presented beside it. The video starts with the first screen where the counter is incremented five times. It then goes to the second page and the button, Page 1 Counter, is also pressed five times.
Returning to the first page in the video reveals the counter is now incremented to 10. You’ve changed the content of the previous screen. It was the line,
Page1().onPressed(), that initiated this process. Looking down now at the screenshot below, it’s the line,
state = StateSet.to<_Page1State>(); , found in the StatefulWidget’s onPressed() function that allows for the count on the first screen to be incremented five more times — but while displaying the second screen.
As introduced in the first article, this was all made possible by accessing the State object from the first StatefulWidget, Page1. The line highlighted below is of a static function called to(), and it is returning the State object of type, _Page1State. Of course, doing so allows for that State object’s property, count, to then be accessed and incremented. Note, that the State object’s setState() function is then called to convey the incrementation on the app’s first screen — again, all while on the second screen. You will find this to be a very very powerful capability in your own Flutter apps.
Find Your State
By the way, every State object in this simple example app is calling an onPressed() function in its corresponding StatefulWidget,
widget.onPressed(). The property,
count, is then accessed right back in the State object to be incremented. A somewhat foolish approach, I know, but instead of its StatefulWidget pretend the State objects are calling some other external object to perform the operation. A scenario you may find yourself in one day with more complex Flutter apps. Today, or simplicity, we’ll keep it to this arrangement in a very simple example app.
Apart Yet Together
Now StatefulWidget and State objects are separate for a reason. A very important reason. One that allows for the declarative platform that is Flutter to function properly. Such a separation is critical to overall performance. You want to keep them separated. StatefulWidgets objects are created, destroyed, and created again all the time in a typical Flutter app. While their State object counterparts (with their mutable data) stay constant — they stay in memory to perform the necessary business logic. In contrast, you should keep anything and everything in a StatefulWidget immutable (final variables, etc.) or face a severe degradation in general performance. That’s why, when you were first learning Flutter, you discovered you weren’t readily given access to a StatefulWidget’s seemingly very important and very useful State object. Until now.
The Type Of State
Below is another screenshot of the first StatefulWidget, Page1, in the example app. Of course, the resulting variable, state, must be of the type, _PageState, as there is no complaint from the compiler when accessing the property, count. With this static function, you have the means to access the StateWidget’s own State object. In fact, depending on the scope you use, you would be able to access any State object anywhere in your app — using this static function from the package, StateSet. Anywhere. Easily.
Get The State
Pressing the button, Page 2, will take you to the second screen of the example app. Of course, it is similar to the first with its own counter and counter button, however, the underlying code demonstrates further features from the StateSet package. On this second page, additional means to retrieve a State object are demonstrated in the onPressed() function in its StatefulWidget. Displayed below on this page, are all the static functions in the StateSet package that retrieves a State object by data type.
With the first function, stateOf(), you retrieve the State object by instead specifying the data type of its StatefulWidget. Note, it’s then cast to the appropriate type so to access the property, count. The second function, of(), uses the StatefulWidget data type to look up the State object while the second data type is used to cast the returned State object by the appropriate type. Lastly, the to() function, one you’ve seen already above, looks for a State object by that specified type and returns an object cast as that type. Three ways to obtain a State object.
Less Look More Feel
By the way, you’ll notice the example app will be calling some functions called buildPage1(), buildPage2(), and buildPage3() to display the actual interface. This is so to direct you more to the onPressed() functions in each StatefulWidget and less on how the interface is constructed. Those functions are really simple too. In fact, they all just call one Stateless Widget called, BuildPage, to present each of the app’s three screens in turn. Today, however, emphasis is more on the StatefulWidget’s onPressed() functions than on that StatelessWidget — although deserving its very own article someday.
The Map Of States
Let’s get right into the StateSet code and show how it’s done. A Map object is used to store and retrieve State objects in the StateSet package. The Map object is called, _statefulStates, and is one of the more important players in the mixin.
There are, however, two other Map objects in play here — and with that, there are the two ways you can retrieve a State object in this mixin: by type or by its own StatefulWidget object. Again, when it comes to ‘by type’, that type can be either by the type of StatefulWidget or by the type of State object itself. The three static functions listed in the screenshot below all access the map object, _setStates, by type. For all three, and type has to be specified (be it a StatefulWidget or a State ), and all three are allowed to return null. I’ll explain that later.
Retain The State
By Flutter’s very nature, its widget tree is ever-changing. StatefulWidgets, for example, are literally being destroyed and re-created again and again in the life of a typical Flutter app, and this Dart package has to be aware of that. Taking advantage of the Flutter framework and how it ‘listens’ for such events makes this approach of storing State objects in Map variables possible. This can be seen in the screenshot below of the didUpdateWidget() function.
When overridden by this mixin, a State object receives its new StatefulWidget in the course of a Flutter app’s life cycle and places it in a static Map object, _statefulStates, replacing its old StatefulWidget that’s soon to be destroyed. It’s in the overridden initState() function, of course, where the Map objects involved are first populated with either the State’s data type or a reference of the State object itself.
An End State
When a State object is to terminate from memory, its dispose() function is called. It’s in this function where the three Map objects are cleared of any reference to that State object. See below. The Flutter framework allows the Dart package to ‘safely’ store and retrieve any and all State objects that may come about in a typical Flutter app and clear memory of them when their jobs are done and they’re to be destroyed.
As we continue through the StateSet class, we see the remaining static functions used to access the three Map objects and consequently the stored State objects. In the screenshot below, the stateOf() function uses the specified type of StatefulWidget to retrieve its accompanying State object if any. Notice how null safety is implemented in all the functions in case the State object was never stored in a Map object in the first place for some reason. In such a case, it returns null. Such circumstances must be anticipated when you offer such capabilities in a public Dart package.
A Type Cast
In the of() function displayed below, you can see two Map objects work together to complete the operation. It’s to retrieve a State object by its StatefulWidget data type and, if found, return the State object cast as its own appropriate data type. Again, you will find such a capability most useful in your future Flutter projects.
Instance Or Type
The to() function below returns a State object cast in the specified type, T. Most of these routines so far are for scenarios where you know the type of State object you’re looking for and you’re able to retrieve that type of State object from a static Map object from anywhere in your app. Keep in mind, however, the nature of Map objects.
Using this particular Map object, _setStates, means you better not have more than one State object of the same ‘type.’ If such a scenario occurs, the ‘second’ State object will replace the first when it is its turn to be stored in this Map object. That’s just how Map objects work — the keys in Maps are unique and each occurs only once. The State objects are being retrieved by type after all. That’s why, in recent versions of this package, there’s another Map object available to you. If you want to store such State objects of the same type, you have the option now to store them by the very instance of their accompanying StatefulWidget instead. The Map’s key is therefore the StatefulWidget object itself!
In the screenshot below, the mixin now has the static function, stateIn(), which either returns null or a State object stored in the map, _statefulState. Such an object is retrieved by the very instance of that State object’s StatefulWidget as the key. Again, the parameter, widget, is allowed to be null resulting in returning null.
The next static function, setStateOf(), uses the stateIn() function and returns a boolean value of true if the setState() function of the retrieved State object is called. Note, it will not attempt the call if the State object is not retrieved, or is not ‘mounted’ in Flutter’s widget tree. Calling the setState() function from an ‘unmounted’ State object will incur an error. Instead, in such cases, a boolean value of false is returned.
You can see in the screenshot, that there are still other static functions that call the setStateOf() function — all returning a boolean value. Developers can then test for true or false when using these static functions.
In Case Of Null
What’s with all the null values, you ask? When providing a Dart package for public use, you have to make the code very reliable and resilient — resilient to misuse or mishap. You have to anticipate the code may be used wrong not at the fault of the developer but because of the circumstance in which it was called. As an example of this, note the StatefulWidget parameter, widget, in all the functions above allows for null. The parameter, func, is also allowed to be null although none of them should ever be null otherwise the intended functionality will not work — the retrieving of a State object will not happen. You may think inserting an assert() would be appropriate — testing while in development that a StatefulWidget and not a null is passed to these functions. However, you’d be wrong. Such a fundamental operation (retrieving a State object) must be adaptive to every scenario — even those when null is passed by mistake or by malice frankly. Regardless, at least such a fundemental operation will not crash!
When writing Dart packages for public consumption, you have to anticipate misuse or mishap. There will be instances, for example, where it’s perfectly normal for a parameter of type StatefulWidget to be null — for the circumstance. The package simply has to respond accordingly. And in this case, for a package that retrieves State objects, the ‘right’ response is to indicate failure: Return null or return false. No State object is retrieved and or no setState() function is called. It's then up to developers, in turn, to respond to such failures accordingly.
The First And Last
We’re nearing the end of this mixin with some getters and an interesting function left to review. As you may know, the getter, root, will return the very first State object that’s using this mixin. For simple and straightforward apps, access to the State object situated at the ‘root’ of the Widget tree is enough to accomplish all that’s required of such a simple app.
Next, the getter, lastContext, returns the ‘latest’ BuildContext object up to that point in the app. You’ll find such an object is essential to search back up the widget tree for specific objects when necessary. Very useful. And finally, there’s the function, _type(), that’s very important to the success of the StateSet class. It’s just a little fancy way of returning the very type specified as it is needed when storing and retrieving State objects ‘by type.’
The Root Of Things
If you’ve read the first article, A Mixin For State Objects Part 1, you may remember the static property, root. It returns the first State object using the SetState mixin, and is used in this example app to assign a new Key value to the StatefulWidget, Page1. A new key causes its accompanying State object to be destroyed and re-created.
Watching the video above, you can see this comes about when the button, ‘New Key for Page 1’, is pressed and results in the counter on Page 1 to reset to zero. That’s because a new key is created for the Page1 StatefulWidget in the setState() function overridden in the screenshot below. Again, with a new key, a new State object is created, and its counter starts at zero of course.
Rebuild The State
In the screenshot below on the third and final page of the example app, there are now even more ways to retrieve a State object. However, two appear to be new-found properties of the StatefulWidget object itself! One is the function, stateAs(), and the other is merely a property called, state. They both come about with the use of an Extension of the StatefulWidget class. All this is demonstrated in the screenshot below — giving you now six ways to retrieve a State object.
Extend The Widget
To make all this possible, there is now an extension of the StatefulWidget in the Dart package, StateSet. Below is a screenshot of that extension. Notice almost all of them are actually calling the Static functions defined in a StateSet mixin.
A Fresh State
You’ve likely discovered when working with Flutter, one of the more common requirements is calling a State object’s setState() function and initiate a ‘rebuild.’ Again, State objects are traditionally not so readily accessible. However, with this StateSet package, you can now initiate such a rebuild using their more readily accessible StatefulWidget. In the screenshot below, you can see there are four more additional properties assigned to the StatefulWidget now to call its associated Stat object’s setState() function, and an additional static function called rebuildState() that takes a StatefulWidget object as a parameter. There is now a total of eight different ways you can call a State object’s setState() function to initiate a rebuild (i.e. call the State object’s build() function again.) and refresh the screen with any updates or changes. Try them. You’ll soon wonder how you lived without them.
To Accommodate Others
You may recognize some of the names of these new additions. The function, notifyListeners(), is familiar to those developers having used the BloC framework or have used Flutter’s own mixin, ChangeNotifier, in the past. They can now use this function instead since it does the very same thing — calls the setState() function of some State object somewhere. The name, refresh(), are for those coming from distant frameworks that had such a function to ‘refresh’ the screen. And the function, rebuild(), is to acknowledge the very fact that the State object is calling its build() function again — to ‘rebuild’ its screen.
Finally, the new setState() function assigned to the StatefulWidget now gives it the power of its State object. In this case, it’s being called with an ‘empty’ VoidCallback function, but of course, the StatefulWidget can now enclose any operation between those curly brackets as its State object’s own setState() function would traditionally do. Of course, it still is, but behind the scenes!
Out of Context
Again, this is such a simple example app, it’s really hard to appreciate the potential here with these new-found functions and features. With the screenshot below, imagine you have some function somewhere that takes in some sort of StatefulWidget and performs some sort of important function. You then have the means to reflect the results on that particular StatefulWidget’s screen — using just one of the five properties now assigned to StatefulWidget objects and listed in the screenshot below.
I can call any of those functions displayed above. In other words, I now have access to the ever-important State object and its setState() function. Anywhere. Easily. Wow.