StateX class topic

The State Extended

Extend the capabilities of Flutter’s own State class. This class allows you to use a State Object Controller (SOC) to reliably call the State object's setState() function from outside its class — a very powerful capability!

Contents
Control _HomePageState Sync Control The Sync Interface? Inherit
Of course, this State subclass has the build() function to return your interface has expected as if using the traditional State class. However, you now have 5 more 'build' functions available to you for even further capabilities. Note, for most of my projects, I use the builder() function as it allows for most of the desired capabilities. All six are listed below in order of precedence:
  • Use the build() function, of course, as you would the original State class.
  • Use the buildF() function instead if you want to have the initAsync() function perform any asynchronous operations before displaying the interface. You see, a FutureBuilder is then utilised. It calls the initAsync() function, and when the asynchronous operations are completed, the buildF() function is then called to display the interface.
  • Next, if you use the buildIn() function instead to return your interface, note that this is a progressive list of capabilities, You're still free to implement the initAsync() function with its FutureBuilder widget, but now an InheritedWidget is involved as well. As just, the buildIn() function will only run once and is never called again. and so only the state() function and dependOnInheritedWidget() function will then dictate which widgets are updated when something changes. Instead of building the interface again and again from scratch, only specified portions on the interface is rebuilt improving performance. Note, using InheritedWidgets in this fashion is an advanced feature, and so by default, is disabled in the StateX class. Pass true to the StateX class' useInherited parameter when you're more comfortable in Flutter and ready to use the built-in InheritedWidget. Three more functions that works with the InheritedWidget are then available to you in the StateX class: didChangeDependencies() , updateShouldNotify() and notifyClient(). Optionally, use the StateIn class that has the InheritedWidget always enabled.
  • Next, use the builder() function that encompasses the capabilities listed above as well as reliably accesses the widget tree's BuildContext variable, context, since it is wrapped by a Builder widget.
  • Finally, there are the build functions: buildAndroid() and buildiOS().
  • Again, you have the option to take advantage of a built-in FutureBuilder and a built-in InheritedWidget or not, but you also have the option to supply both the Material interface and the Cupertino interface to your app. The framework will run one or the other depending on whether your app is running on a Android phone or iOS phone. As always, you have options. If you don't want to the Cupertino interface, simply implement the buildAndroid() function, and it will run by default---even on an iOS phone.

Like any good app framework, you've got options. From left to right, the number of options only grows:

build() -> buildF() -> buildIn() -> builder() -> buildAndroid() -> buildiOS()

Handle Your Errors

Error handling should always be top of mind when developing software, and so every StateX object now has the onError() function that's called if an error occurs in the State object. This allows you to 'clean up' and fail gracefully. And remember, the State Object Controller separates that interface (i.e. the State object's build() function) from the business rules and event handling. Such an arrangement has proven to be very powerful encouraging a clean architecture within the State class itself!

Control Your Code

All your ‘mutable’ code should go into your State Object Controller. It would contain the ‘business logic’ involved in a given app as well as address any event handling. Controllers are not new to Flutter. For example, two popular widgets that use a controller is the TextField widget and the SingleChildScrollView widget.

However, unlike those two widgets, a StateX object can have any number of controllers. This promotes more modular development where each controller is dedicated to a particular responsibility independent of other controllers. This further relieves any 'controller bloat' common when involving a controller (see State Object Controller).

The State Pattern

A common pattern seen when implementing a controller for use by a StateX object will have the controller object instantiated right in the State object and then referenced here and there in one of the StateX object’s many 'build' functions. Thus providing the data and the event handling. For example, the first screenshot below is of the 'counter example app' that accompanies the Flutter Framework package. The second screenshot shows how a number of controllers can be added.
(A controller and its use are highlighted by red arrows.)
_MyHomePageState Page1State

Sync The State

There's now a means to deal with asynchronous operations in the State object before rendering its interface. In other packages, the approach has been to execute such operations even before the app begins. It's a disjointed approach. The operation is not done by the actually State object (the screen) where it's relevant or required. However, Flutter has always had the FutureBuilder widget to make this possible. A FutureBuilder widget is built into the StateX class, and has its initAsync() function to then perform such asynchronous operations. As a result, when such a State object is called, it can wait for its asynchronous operations to complete before proceeding. As easy as that.

Below are four gif files. The first one depicts what a user will see more often than not when starting up an app written with the Fluttery Framework. There's always a remote database to open or web services to connect to and this takes a little time. You don't want your user staring at a blank screen. They'll think the app is frozen! Fluttery displays a spinner indicating the app is indeed running. The second gif file depicts twelve separate StateX objects waiting to continue. Each has its own individual asynchronous operation loading a animal graphic from some REST api somewhere. The third gif file shows the whole startup process for this particular app. It carry's on and shows you they are indeed individual operations ending with a different picture. Note, in the last gif file, you've the option to implement a splash screen instead.

Since I'm using an Android emulator, those spinners are from the CircularProgressIndicator widget. If they were running on an iOS phone, the CupertinoActivityIndicator widget would produce the iOS-style activity indicators instead. Flutter is a cross-platform SDK after all.
image_api_controller.dart working_memory_app.dart

Control The Sync

Those three screens above are from yet another example app supplied with the Flutter Framework. Yes, there are twelve separate StateX objects on that one screen each loading an animal image. Actually its their own individual State Object Controller that's performing the asynchronous download. Above, is a screenshot of that controller's own initAsync() function. While each controller is calling its _loadImage() function, its associated StateX object will quietly wait with its indicator spinning away on the screen. Very nice.

The screenshot on the right is another initAsync() function from another app altogether. It demonstrates there can be a number of asynchronous operations performed before an app can continue. This particular app involves the authentication of the user for example. If not already logged in a login screen will appear. This function is in another controller and that controller calls yet another controller to run its own initAsync(). See how you're able to separate distinct asynchronous operations each prefixed with an await operator and returning a boolean value to the variable, init. It's all easy to read and all in the right location to be implemented.

It's suggested you implement such operations in a Controller, and not directly in a StateX object. Besides, the StateX object's own initAsync() function is already implemented: It's calling all the initAsync() functions from its associated State Object Controllers. When they're all complete, the StateX object only then calls its build() function. Since most asynchronous operations have no direct relation to an app’s interface, you’ll likely have your asynchronous stuff running in a State Object Controller anyway with the rest of the app’s business logic. See how that works?

There can be individual controllers running their own initAsync() function. Very clean. Very modular.

Which Interface?

As you know, Google touts Flutter as a cross-platform SDK. It even offers two types of interfaces to be displayed to users when running a Flutter app: the Material interface design and the Cupertino interface design.

This suggests then that you're able to create an app using either interface or both. For example, you'd run the Cupertino version of the interface if the app is running on a iOS phone , and run the Material version if the app is running on an Android, or on Windows or on the Web or on Linux. However, right out of the box, Flutter doesn't give you any place to make such a distinction. It only gives you one function: the build() function. However, the Fluttery Framework already uses that function. It uses that funtion to give you a separate place for those two types of interfaces:

build Like the State class, the StateX class is an abstract class. However, when you extend the StateX class, you don't have to implement the one build() function. You have to implement two functions:

app_state.dart

Flutter produces one codebase for multiple platforms. It's likely the very reason you've turned to Flutter to make your next app is because of this ability. If you only want to use the Cupertino interface, for example, then direct the buildAndroid() function to call the buildiOS() function. Otherwise, build both. As you see from the video, with the Fluttery Framework, you can even have to user choose their favorite interface at runtime.

Inherit The State

You've may have been introduced to the InheritedWidget, and how it allows you to repel down the widget tree any piece of data you've designated. However, a more intriguing feature is whenever you call an already instantiated InheritedWidget, any widgets assigned as its dependents are rebuilt (their build() functions run again)---as if their setState() functions were explicitly called.Now that allows for improved performance with the refresh of only specific areas of the interface.

Even the efficiency of the humble 'counter app' is greatly improved. Instead of refreshing the whole screen (including the StatelessWidget with its 'You have pushed the button this many times') , when the 'Use the built-in InheritedWidget' switch is on, only the lone widget displaying the current count is then ever rebuilt. The rest of the screen would now be left alone with every press of that button. Granted this is a very simple interface and possibly a bad example, but look how easy this is implemented in the screenshot below.
counter_app.dart

The state() function found only in the Fluttery Framework will allow for this immediate improvement in efficiency. When it comes to interfaces, the less that's rebuilt, the better. You're app is running on a mobile phone and not a Cray Supercomputer after all.

Back to the app with its grid of animal pictures, you can see above when the 'new dogs' text button is pressed, only the three 'dog pictures' are downloaded again. What your seeing are only three portions of the screen being updated---only three widgets being rebuilt. If the whole screen was rebuilt, all the pictures would change. Not very effective.

counter_app.dart

The screenshot above depicts one of the three widgets being assigned as a dependent to a State object's InheritedWidget using the dependOnInheritedWidget() function. In fact, the State object's controller with its own dependOnInheritedWidget() function is actually used.

Classes

AppStateX<T extends StatefulWidget> Get started StateX class AppState class Error handling
The View for the app. The 'look and feel' for the whole app.
StateX<T extends StatefulWidget> Get started StateX class Testing
The extension of the State class.