Greg Perry

Nov 26, 2021

5 min read

A Better Flutter App #3

Switch between the Material and Cupertino interface at will.

One of a series of articles detailing a comprehensive starter app. This app is generated by a template offered by the package, app_template, which uses the underlying framework, mvc_application, based on the MVC design pattern.

I Like Screenshots. Tap Caption For Gists.

No Moving Pictures, No Social Media

Let’s begin.

Other Stories by Greg Perry

In this article, I’ll showcase Flutter’s ability to present your app either in the Material interface design usually designated for Android phones or in the Cupertino interface design that’s long being the chosen ‘look and feel’ for iOS phones.

The underlying framework used by the starter app, of course, can tell the developer which interface design would be appropriate as it can detect which platform it is currently running on. In this article, I will present to you how this starter app indeed accommodates this ability and displays the appropriate interface using either ‘Material’ widgets or ‘Cupertino’ widgets.

Three different apps at startup

Looking at the ‘App State object’ for this starter app called, TemplateView, we can see the parameter, switchUI, is used to ‘switch’ to the opposite interface from the app’s intended interface. The emulator you see depicted in the gif files is of an Android phone, and so, when the parameter value for the switchUI parameter is set to true, the framework will indicate to the developer that the Cupertino interface design and not the Material interface design should be displayed.

In other words, when switchUI is set to true, the AppsState class will set some additional property fields as well. As you see in the screenshot below on the right-hand side, in this case, the instance variable, useMaterial, is set to false and the instance variable, useCupertino, is set to true. The developer, when writing their app, will indirectly use these variables to determine which ‘interface’ is to be displayed. I’ll demonstrate this next.

When you examine the starter app and its three separate apps, you’ll notice that their State objects have an interesting computation going on in each of their build() functions. For example, below are screenshots of the build() function for the ‘counter’ app and for the ‘word pairing’ app. Both utilize the framework's static variable called, App.useMaterial, to determine which StatelessWidget to return. As you may have guessed, one StatelessWidget uses Material widgets while the other uses Cupertino widgets. That static variable has access to those property fields mentioned earlier.

The third State object for the ‘Contacts’ app works with the very same static variable to call, in this case, the appropriate lambda functions (high-level function) — each with their particular ‘Material’ or ‘Cupertino’ widgets. It could just as well have been StatelessWidgets — I’m just demonstrating an alternative approach. If you examine now the lower right screenshot, you can see the static variable, App.useMaterial, is a rather elaborate and complicated expression — all to determine the correct boolean value based on the circumstances at hand. This is indicative and much of the underlying framework — since it performs much of the heavy-lifting so a developer can just be concerned with their immediate work.

With that, the framework performs some other needed adjustments when the starter app switches over to the other and often opposing interface design. You see, many of the widgets in Flutter will not function properly and will, in fact, throw an error if the right ‘root widget’ is not also used. It’s either a MaterialApp or a CupertinoApp widget at the root of the widget tree to dictate what subsequent widgets can be called and displayed. In other words, when a switch is made in the interface, it’s necessary for the whole ‘widget tree’ to be rebuilt starting with the appropriate ‘root app’ widget.

And so, deep in the framework, it’s the other static variable, useCupertino, that is instead used to determine which of those root widgets will start the widget tree. Note, it could have just as easily been the variable, useMaterial, as well. Both static variables are available to developers, of course, allowing for personal preference. Frameworks are to allow developers options.

The last two screenshots below are of those two lambda functions used by the Contacts app. Again, if all is set up correctly, the widgets highlighted below will successfully build and display the appropriate ‘look and behavior’ depending on whether the Android-style or the iOS-style is desired.

This is yet another feature every published Flutter app should have. After all, the one main argument Google makes for shops to migrate to Flutter is the ability to run one codebase on multiple platforms. The framework, mvc_application, allows your app to ‘look the part’ depending on the platform.

Cheers.

A Better Flutter App #4

Localization made easy

→ Other Stories by Greg Perry