Get started topic
State Extended
The StateX class is truly an extension of Flutter's State class providing functions and features found lacking in the original. These features are nothing new to Flutter. StateX is an amalgamation of other features already found in Flutter. My motto when writing this package has been, 'Keep it simple, Keep it Flutter.'
Wait For The Future | Control The State | State The Control | Abstract Control | The Source of Control | On The Menu | Set The State | A Single Control |
When it comes to building your interface with this State class, you've got options:
-
If you use the traditional build() function then you're using the StateX class like Flutter's original State class.
-
If you use the buildF() function then you have the initAsync() function to perform any asynchronous operations. A FutureBuilder calls the initAsync() function to complete these operations before calling the buildF() function that then displays the interface.
-
If you instead use the buildIn() function to return your interface, it will only run once and is never called again. An InheritedWidget is involved here, and so only the state() function and dependOnInheritedWidget() function will dictate which widgets are then updated when something changes. Instead of building the interface again from scratch, only specified portions on the interface is rebuilt improving performance.
-
If you use the builder() function instead, it's wrapped in a Builder widget so to safely access the widget tree's BuildContext variable, context. If you don't use the builder() function, it instead gives you access to the two final build functions.
-
buildAndroid() and buildiOS() supplies the Material interface and the Cupertino interface respectively depending on whether you're app is running on a Android phone or iOS phone respectively.
So, why are these other 'build' functions available? Because, they're needed...like, all the time. All my apps needed a FutureBuilder to perform time-consuming operations before the app could proceed. And as I worked with Flutter, I came to appreciate the benefits of rebuilding as little as possible with every change of the interface. Granted, Flutter's widget tree helps immensely with its pseudo-declarative approach of rebuilding from scratch, but in the end, the less rebuilt, the better the performance, and the InheritedWidget allows for this.
Let's run through the example app found in the README.md file and demonstrate these features. After this, you could turn to the extensive example app that comes with the package to learn more.
Wait For The Future
Time and time again, you'll find that some web service has to be opened or some database accessed before a screen can then display the remote data. In Flutter, these asynchronous operations can easily be handled by Flutter's FutureBuilder, but a State object doesn't have a FutureBuilder... until now.
In the video file, you see the README example app starting up and its circular progress indicator rotating in the center of the screen. The video is demonstrating the built-in FutureBuilder now available to you. Instead of a circular progress indicator, you can provide a splash screen by returning a widget in the onSplashScreen() function. When there's work to be done before a State object can then display it's interface to the screen, you now turn to its initAsync() function.
In this simple example, it's merely a time-delay of 10 seconds being executed, but it could just as easily be an authentication routine and or some REST API calls needed to first retrieve the data then displayed on the screen. The first screenshot below is of the State class, _MyAppState. It is a subclass of the StateX class and takes in a controller called, AppController. Being the first or 'root' State object for the app, it directly extends the class, AppStateX. The State Object Controller, AppController, is presented in the second screenshot below. Note, it has an initAsync() function with the 10-second delay. The controller has been delegated to perform the asynchronous operation. I would encourage that controllers be delegated to the app's event handling and business rules. Again, nothing new to Flutter. The widgets, ListView and TextFormField to name a few delegate specific tasks to a controller.
_MyAppState | AppController |
---|
Control The State
In the video below, you see this simple example app starts up again. Once it's up and running, the button is pressed 10 times and the app is then terminated. With every tap of the button, the counter increments accordingly. Note, as simply as this app is, the only widget that is being updated is the Text widget containing the count. Everything else (the Scaffold widget, its AppBar containing the title, 'StateX Demo App', the Text widget with the string, 'You have pushed the button this many times:', etc.) has been executed once at start up never to be touched again. That's because the buildIn() function was used in the State class, _MyHomePageState. to display the count
The screenshot highlights what happens when that button is pressed. Again, when using the StateX class, a controller is typically delegated to handle any events, and so the controller's corresponding onPressed() function is called with every tap of the button. This separation of the interface from the logic makes for more scalable and more maintainable software.You'll find the StateXController class an indispensable companion to the StateX class. When taken into such a State object, the controller easily 'retains' the state of the overall app. With the logic, business rules and event handling residing in a class that, unlike the StatelessWidget and State class, is not tethered to a interface allowing for mutable content brings about some dynamic capabilities and an unlimited degree of abstraction.
_MyHomePageState |
---|
State The Control
The first screenshot below is of the State class, _MyHomePageState, that displays the count. You'll see the count is encapsulated in yet another StatefulWidget called, CounterWidget. Such an arrangement would keep the count unchanged even if the buildIn() function used by this State class was instead the traditional build() function. The CounterWidget has got its own State class. Conventionally, you would have to call the setState() function for that particular State object.
Remember, when the buildIn() function is used, it's only called once --its content is displayed and would never change if not for the state() function and or the dependOnInheritedWidget() function. You can see the dependOnInheritedWidget() function in the second screenshot below. It shows the build() function for the CounterWidget's State class.
The dependOnInheritedWidget() function will make the CounterWidget a 'client' of the State object, _MyHomePageState.
More specifically, it will depend on State object's built-in InheritedWidget.
And so, every time the button is pressed, and the controller's function, con.onPressed()
, is called,
the count is incremented. However, then the controller's notifyClients() function is also called.
The CounterWidget is one of these 'clients' now, and its build() function will then be called
displaying the new count and leaving the rest of the interface untouched.
Very nice.
_MyHomePageState | _CounterState |
---|
Abstract Control
Let's continue to examine the benefits of using the State Object Controllers. The very simple Counter app we've been examining does have some more features. There's the original integer counter, granted, but there's two other counts available to you. You can also tap through the alphabet or through the prime numbers between 1 and 1000 if you like. There's a popup menu that presents you with all three options.
What I'm emphasizing here is the easy separation of the app's interface from its logic with the use of controllers. The level of abstraction available to you when using these accompanying controllers with your State objects will make you wonder how you ever did without. It's a very very very simple app, but it's hoped the advantages of using controllers will only become more self-evident.
Note, in the CounterWidget, the line, Text(con.data, style: Theme.of(context).textTheme.headlineMedium);
,
is left untouched as you hop from one count option to another listed in the menu.
As far as the interface side is concerned, the con.data
simply returns a String (see below).
There's no sign of what the data is or how or where it comes from.
A Clean Architecture, for example, would have the StatelessWidgets and the StatefulWidgets
that make up the interface only concerned with 'how the data' is presented not 'what the data' is.
State Object Controllers makes this readily possible.
_CounterState | HomeController |
---|
The Source of Control
In the second screenshot above, it's abundantly clear there's a lot going on in the HomeController's getter called, data. The code on the interface side, gives no hint there's actually three data sources used by the app. It's the controller's responsibility to properly deal with 'the data' supplied to this app. It's the controller's responsibility, for example, to convert the data to a String so to accommodate the Text widget. It's the controller that's concerned with what happens when the app starts up or when a button is pressed. That distinction makes for more effective, more scalable coding frankly.
Back to the HomeController class. It's displayed in first screenshot below instantiating the three data sources it's to works with. The interface code doesn't need to know 'how to talk' to these data sources. The controller does that. It's also responsible for the event handling, and so it's onPressed() function is a bit busy as well (see the second screenshot below). The controller increments the appropriate source depending on what was selected up on the menu.
HomeController | HomeController |
---|
On The Menu
As for the popup menu, in the _MyAppState class, it's supplied from the controller, HomeController (see below). Once a menu option is selected, you'll notice in the second screenshot below, the controller's setState() function is called. That will refresh the last State class the controller registered with (i.e. _MyHomePageState) and the new count will appear on the screen (see the video below).
_MyHomePageState | HomeController |
---|
Set The State
By the way, in this simple app, we're confident the controller's setState() function will call the setState() function for the State object, _MyHomePageState. However, in other circumstances, the specific State object would have to be retrieved to ensure its setState() function is called. The State Object Controller, once registered with any number of State objects, is able to retrieve a specific State object. Note, if the specified type was not a State object taken in by the controller at that time, null is returned instead (see screenshot below).
HomeController |
---|
A Single Control
Despite, being a very simple app, there is more than one controller. Each delegated to a particular 'area of responsibility' for the app. Remember, there is the, AppController. Its job is to get the app ready at startup and essentially address the 'look and behavior' of the app overall. The other controller, HomeController, was assigned to the home page. It's code is concerned with the logic required by the home page. This delegation of work between each State class using controllers allows for also very efficient coding and maintainability.
You may have noticed, in most cases, each controller class uses a factory constructor (see below). Of course, not a hardened rule, but I've found it's most suitable for the role it plays to always have just a single instance of a State Object Controller. This can be described as using the Singleton Pattern for controllers. Only one single instance of the class is ever created no matter the number of calls to its constructors. It's another characteristic you're unable to attain with a State class and one that's very advantegous to the overall dynamics of the running app.
AppController | HomeController |
---|
For example, note how the controller can merely be instantiated in far-flung parts of the app and easily supply the 'current state' or the necessary values and properties for a functioning app? 'Keep it Simple, Keep it Flutter' was the motto, remember? The first screenshot below is the State class, _MyHomePageState. It's controller is simply instantiated in its constructor---so the controller then has access to it. In the second screenshot, the controller is instantiated so its State object can rebuild the widget, CounterWidget, whenever calling its setState() function.
_MyHomePageState | _CounterState |
---|
Libraries
- state_extended Get started
- The extension of the State class.
Classes
-
AppStateX<
T extends StatefulWidget> Get started StateX class AppStateX class - The StateX object at the 'app level.' Used to effect the whole app by being the 'root' of first State object instantiated.
-
StateX<
T extends StatefulWidget> Get started StateX class Using FutureBuilder Error handling Testing Event handling - The extension of the State class.
- StateXController Get started State Object Controller Testing Event handling
- Your 'working' class most concerned with the app's functionality. Add it to a 'StateX' object to associate it with that State object.