Toggle Your Flutter
By: Greg Perry
Tags: iOS App Development, Android App Development, Flutter, Software Development, Programming

Both build() functions have the same double-arrow icon in their navigation bars, and both utilize the same function in the _AppEventHandler class called, leading. By the way, a screenshot below of the ‘Material’ interface design highlights that icon — you can’t miss it.

Let’s take a quick peek at that function called, leading, in the class, _AppEventHandler. You can see below it, in turn, calls a function with a more descriptive name. Further, you can see the switching of the interface design would seem to come down to the toggling of a boolean instance field called, useMaterial. You’ll discover, in fact, that that is a getter & a setter in that class.

Set The Interface

Note, in the screenshot below, the setter involves a couple of static instance fields. Being static they retain their values for the lifetime of the class, _AppEventHandler — in any number of instances of that class. Assigning a boolean value to the setter sets the appropriate values to those static fields. They effectively serve as conditional flags and can be readily accessed with every new build. Are you beginning to see how things are working here?

Well, how does the next build come about, and more importantly, what gets rebuilt? Let’s look at the ‘switch interface’ function again, and note the refresh() function coming from that object, appState. See what’s happening?

That instance field, appState, references the first State object, _MyAppState. _MyAppState is a subclass of the StateBloc class which has the refresh() function. The StateBloc class extends Flutter’s State class. There’s a screenshot of that refresh() function below. It’s calling the State class’ setState() function with an empty lambda in it. Guess what that does.

It results in the _MyAppState object’s build() function eventually being called again. And, as you know, there’s some logic in that build() function used to determine which interface design to use. See how it all works now?

CClicking on that double-arrow icon, a particular State object calls its setState() function marking the element as ‘dirty’ (to be rebuilt) and its StatefulWidget is then added to the global list of widgets to be rebuilt in the next scheduled frame. As it happens, the State object itself is also re-created resulting in that whole branch of the widget tree being rebuilt, and in this simple app, that means the whole app is rebuilt. Easy, peasy.

And so, looking at the screenshot below, we see a ‘design’ widget is being instantiated again and again with every build. However, note the little red arrow. The StatefulWidget, _RandomWords, is being instantiated again. That’s alright — StatefulWidgets get re-instantiated all the time in Flutter. However, what’s the deal with assigning a new unique key every time?

New Key, New State

With a new unique key assigned in the StatefulWidget, Flutter will now call that StatefulWidget’s createState() function re-creating its State object. If a new unique key wasn’t assigned to the StatefulWidget, by design, the State object should remain in memory — retaining its state. Granted, there are instances where the State object is considered ‘dirty’ as well, but this approach assures it will always be rebuilt. Most times, it is not to be rebuilt. After all, in Flutter, though a StatefulWidget is destroyed and rebuilt many times, its State object is to remain — to retain its state as part of Flutter’s State Management.

However, a new unique key assures a State object’s rebuild. Now, look below and see what that means. It means the home screen will then display the appropriate ‘look and feel’ depending on its designated interface design. The conditional expression,? :, makes sure of that.

By the way, below is a screenshot of simply returning the State class, _RandomWordsAndroid, every time — even though the designated interface design is Cupertino. You can see they don’t mix and match, and so, the ‘App Event Handler’ is utilized throughout the app to ‘retain a consistent state’ — a consistent look and feel in this case.

Also, notice how the _AppEventHandler class is all self-contained. When first instantiated, it determines which platform the app is running in. Further, the first State object it registers is then designated the ‘app state.’ It’s the app state that’s rebuilt again and again when the double-arrow icon is tapped on. With its static fields, the _AppEventHandler class retains its current state — that state being ‘what could and should be the current interface design with every rebuild.’ Lastly, notice in the screenshot below, the class uses a factory constructor (singleton pattern) ensuring only one instance of this class for the lifetime of the app — further retaining a consistent state.

Also note, any function calls in this class involve instance fields inside the very same class. There’s high cohesion here. There’s some good parameter validation as well. For example, if there’s no ‘app state’ specified (no StateBloc object supplied to the constructor), there’s no error — simply nothing happens when the user taps on those double arrows for example. That’s it. A developer will notice that problem. Users won’t be calling your company complaining there’s an error — “it’s just not working??”, they’ll say.

Note the low coupling. The rest of the app, for example, doesn’t know exactly how the _AppEventHandler class determines the current platform used. It doesn’t need to. That means if we change ‘the means’ used to determine the current platform, the rest of the app will be none the wiser. For example, the class, Platform in dart:io, doesn’t currently allow this simple app to work on the Web. However, that can easily be solved by switching it out with the package, universal_platform, allowing the app to also run on the Web.

Divide And Conquer Code

The code would have to change a bit, however. I mean, first and foremost, I feel it’s best to separate the ‘logic’ from the ‘interface’ of an app into different Dart files altogether. However, being such a simple example app and for brevity, that wasn’t done here. It’s my usual practice, however, to include only the import statements that are relevant to the code found in a particular Dart file. It’s a standard approach. A pattern? A pattern!

If the class, AppEventHandler (no leading underscore), was in a separate Dart file; a separate library file, no other part of the app would know it’s now importing the package, universal_platform, for example. The app could now even run on the Web. And if on the Web, let’s say it’s to use the Material interface design. ok? Again, the logic is all encapsulated in one place. Any such changes are isolated to this one class. Modular approach. Makes for better maintainability.

Reuse The Code

Looking closely at the code in the class, _AppEventHandler, you can see there are other patterns in play. Inheritance, of course, allows you to take advantage of reusable code (the code in the parent class) and yet adjust and enhance the same operation to the particular needs of the child class.

As an example, the function, addState(), is called in the private constructor called, in turn, by the factory constructor when the class is instantiated for the very first time. It’s a function originally found in the parent class, Bloc, and serves to designate the specified State object as the Bloc’s ‘current’ State object. — it now is to work with that State object. You see, a single Bloc should be able to work with more than one State object for the duration of the Bloc’s lifecycle — makes for a more useful Business Logic Component. No?

You see the first time the addState() function is called, the ‘app state’ is forever assigned. The way this is done is subtle but effective. The assign-if-null operator, ??=, records only the first occurrence when a state object is passed to the function, addState(). Any subsequent calls to the function, addState(), allows the Bloc to ‘talk to’ a different State object (the most recent State object) by design. However, when it comes to calling the switchUI() function (through the leading() function), only the ‘app state’ object is refreshed. See how that works?

If later we no longer like that particular approach, we can make a change sometime in the future. The change will likely only occur in this class— only affecting this class. There’s room for scaling since the code is modular.

The State of Abstraction

Did you notice the inheritance utilized when it came to those two State objects? Both the ‘Android’ state class and the ‘iOS’ state class extends from the class, _RandomWordsState. Why did I choose to do that? Repeated code.