Part of the ‘A Work in Progress’ Series
In this article, I’ll walk through how the MVC framework library package allows a user to ‘switch’ the color theme of a Flutter app with a click of the mouse. The code involved will be reviewed giving you further insight into how the MVC framework is set up to allow such capabilities in your own Flutter app.
A Work In Progress
This is part of the ‘A Work in Progress’ series of articles covering the progress of a simple ‘ToDo’ app I’m working on called WorkingMemory. The intent of this series is to document the implementation of every and all aspects of this app, its construction as well as its use of the MVC framework library package, mvc_application.
I Like Screenshots. Click For Gists.
As always, I prefer using screenshots over gists to show concepts rather than just show code in my articles. I find them easier to work with, and easier to read. However, you can click or tap on them to see the code in a gist or in Github. Ironically, it’s better to read this article about mobile development on your computer than on your phone. Besides, we mostly program on our computers and not on our phones. For now.
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
In truth, this article was inspired by Ritesh Sharma’s own article, Switching themes in Flutter apps like a fox! His approach involved using Norbert Kozsir’s own library package, dynamic_theme, to help in dynamically switching an app’s theme when a user selects one from a menu dropdown.
It’s A Wrapping Theme
I noted to use the DynamicTheme library package, it has to be the ‘root widget’ for the app (i.e. the first State object instantiated for the app). Implementing a library package as the ‘root widget’ has been a practice I’ve seen many times before. You see, doing so ensures the library package’s setState() function calls will then rebuild the MaterialApp widget or the CupertinoApp widget it wraps around.
For example, below is a screenshot Ritesh Sharma’s sample code where you can readily see the MaterialApp widget is indeed enclosed by the DynamicTheme widget. Personally, I don’t like this approach. What if you have other widgets that need to achieve the same ability? Are you then endlessly wrapping widgets around other widgets? It would get pretty ugly frankly when all that’s needed is ready access to a particular State object.
All you need is access to the State object whose build() function contains the MaterialApp or CupertinoApp widget for your app. When you have access to that State object, you have access to its setState() function. Do you see what I mean? Anyway, there’s another approach. A simpler, cleaner approach in my opinion. This approach involves a ready-made framework that provides access to that particular State object. That’s what this article is all about.
Set Your Theme
Before we look at this alternative, let’s continue with this approach so as to understand the fundamentals involved in achieving this ‘dynamic theming.’ And so, when changing the app’s theme, it all comes down to calling the MaterialApp widget or CupertinoApp widget again, but with a different value for its named parameter, theme. That means it all comes down to calling the State object’s build() function that contains that MaterialApp widget or CupertinoApp widget, and of course, that’s achieved by calling that State object’s setState() function. Follow so far?
You can see this process depicted below in the screenshot on the left-hand side. When a new theme is selected, for example, the ‘themedWidgetBuilder’ function is called again and returns the app’s MaterialApp widget, but not before passing in that variable, theme, of type, ThemeData. That tells you the ‘themedWidgetBuilder’ function is called again and again in some State object’s build() function somewhere, and indeed, that is the case when you now look below at the screenshot on the right-hand side.
Note, the ‘theme’ is passed in from a private instance variable called, _data. The build() function is called again and again, as you know, with every call to the State object’s setState() function. And so, in a nutshell, that’s how the ‘dynamic change’ in the app’s theme is achieved. However, I will explain further and show you when and where the setState() function is called.
Change Your Theme
In Ritesh Sharma’s sample code, when a user does select another theme option from the menu dropdown, the function, changeColor(), is called. It’s there where the function, DynamicTheme.of(), obtains the State object, DynamicThemeState. That State object’s setThemeData() function is then called passing in the selected ‘ThemeData’ value. This is all highlighted by the first red arrow below.
That State object, DynamicThemeState, is found in Norbert Kozsir’s library package, dynamic_theme, and it’s there where the function, setThemeData(), assigns the selected ThemeData to the private instance variable called, _data. Remember that variable? Note, it does this while enclosed in the State object’s setState() function — and there you have it. That, in turn, will cause the State object’s build() function to be called again. Hence, the MaterialApp widget to be called again and passed the selected ThemeData object to its named parameter, theme. The ‘colour theme’ for the App is thus dynamically changed. Easy peasy.
So again, the critical thing here is to call the setState() function for the ‘root’ State object if and when you want to change an aspect of the whole app.
My Own Theme
My ‘ToDo’ app, WorkingMemory, has the same ability. It too can change its ‘theme’ with a selection from a list. It too uses the same underlying mechanism that calls, again and again, the State object’s build() function containing the MaterialApp widget or the CupertnoApp widget — depending on the running platform. Of course, this app uses the MVC framework supplied by the library package, mvc_application, and so it’s that framework that provides the means to perform such an operation.
However, unlike the DynamicTheme package with its DynamicTheme.of() searching the widget tree for a particular State object, the framework always has ready access to that particular State object of interest — the one comprising the ‘root widget’ for any Flutter app build upon the framework. The screenshot below conveys where a breakpoint was put in place. It stops the execution of the framework where the named parameter, theme, for the MaterialApp widget, is assigned the appropriate ThemeData value. In this case, the code displayed here is found in the State object called, AppView.
It’s a framework, and so unlike Ritesh Sharma’s sample code, the screenshot above looks a lot busier with all the possible parameters assigned to the MaterialApp widget. Note, that’s a common characteristic of such a utility class — a lot of parameter validation. Of course, you’re not obligated to supply every parameter value. For example, in the screenshot below of my ‘ToDo’ app there are only two parameter values passed. The rest are validated with default values like any other Flutter app. But I digress.
Your Theme Menu
Like Sharma’s example, the WorkingMemory app has an array of colour themes available in a dropdown menu. The screenshot below on the left-hand side displays the initState() function of the class, AppViewState. Note, AppView extends class, AppViewState, in this framework and so it’s all the same ‘root widget’ for the Flutter app. Below, you’re seeing the app’s menu being initialized in the State object’s initState() function. Part of its initialization is to return the app to the last selected colour theme, and like Sharma’s own sample code, SharedPreferences is used to determine the last selected colour theme.
The gif displayed below on the right-hand side shows the opening of a colour picker from the menu dropdown and the selection of a new colour theme.
Now, of course, you’re not obligated to use this ‘App menu’ at all with your next Flutter app. However, like any good framework, it’s there as an option. I’ve used this framework in another sample app called, Bizaar and there’s no menu dropdown in that app at all. However, it also changes the app’s ‘theme’ in another respect. You can read more about that in the TL;DR section below if you like.
What’s Your Theme
In this WorkingMemory app, however, I incorporate the default menu dropdown offered by the framework. Doing so, you then have the standard ‘About’ option available to you as well as this cute little ‘colour picker’ to change the colour theme of your app. A screenshot of the Appmenu class’ init() function, reveals how the last selected colour theme is reassigned to the app. We’ll take a look inside that onChange() function next.
What’s Your Preference
Preserving your preferences is such a common feature found in mobile apps, the ‘Shared Preferences’ capability is engrained in the framework. In both the function, onChange(), and the getter, colorSwatch, the library package, Prefs, is used. Like Ritesh Sharma’s sample code, Prefs works with the plugin, shared_preferences, to preserve and then retrieve the last selected colour theme. In my case, I wrote the library package, Prefs, to make working with the plugin just a little easier.
A Theme Change
When the colour picker is opened and a new colour picked, the function, onChange(), is called. All the magic happens in this function. A screenshot below of the function shows you the selected ColorSwatch value is merely assigned to the app’s themeData property and then the State object’s refresh() function is called. Of course, there’s a bit more going on in the underlying code, but that’s pretty much it on this side. Note, if you’re app is running in iOS, there’s a ‘Cupertino’ counterpart receiving the selected colour as well. Note, the breakpoint below highlights the function, Prefs.setInt(). It’s recording the selected value in Shared Preference for the next time the app starts up.
The call to the refresh() function in the screenshot above eventually results in the AppView State object running its build() function again. As you know, that means the MaterialApp widget is built again, and as you see below where the breakpoint has stopped code execution, the named parameter, theme, is again assigned the app property, App.themeData. The value of which was changed in the screenshot above.
Options are the Theme
Again, being a framework, you’ll notice, at that breakpoint, there are two other sources for the named parameter, theme. Why? Because! That’s why. A framework must allow for other circumstances that may dictate how the app’s theme is generated from one particular Flutter app to another.
For example, I could have just as easily assigned a ‘theme’ to the ‘View’ class in my ‘ToDo’ app. A screenshot below of my ‘ToDo’ app where there was once only two parameter values now has a third parameter value — an elaborate theme is now explicitly assigned to the named parameter, theme.
You see, in this framework, the property, theme, takes precedence over the App property, App.themeData. Of course, like any good framework, there’s yet another option. Heaven knows how the theme is conceded by some apps. Maybe the app reads a file or a database to get the app’s theme. Maybe the Internet is involved. Regardless, that’s where the function, onTheme(), comes in.
In the screenshot below I’ve instead introduced the ‘purple-amber’ colour theme using the function, onTheme(), instead of explicitly passing it to the named parameter, theme. This is just to demonstrate you have options in this regard. You can have anything in that function. What do I know?! It’s your app! Of course, having seen the AppView code above, you now realize every single parameter eventually passed to the MaterialApp widget or CupertinoApp widget also has such options. Nice.
And so, looking below at the build() function for the AppView State object again, you can see how the ‘if null’ operator, ??, enforces this order of precedence leaving the property, App.themeData, providing the default theme value. It’s that property we change in the App’s menu dropdown. Get it now?
Refresh Your Theme
Let’s go back to the onChange() function and examine, once the property, App.themeData, is assigned a new value, how the App’s MaterialApp widget is called again and rebuilt? Well, like Ritesh Sharma’s sample code, the setState() function for the ‘Root Widget’ is indeed called. However, this is done by the framework without the use of a ‘wrapper’ class. When the framework first starts up, ready access to the ‘root’ State object is made available right away if and when the developer needs it. Let’s show you now how that particular setState() function is called.
Looking at the screenshot above, note the function, refresh(). Now, it’s not a standard Flutter function used to rebuild an app’s interface. You’re likely familiar with the function, setState(), performing that role instead. No, this function, refresh(), is found in the framework and does a few things more than just call the current State object’s setState() function.
Below is a screenshot of that particular refresh() function. It’s calling its parent class’ refresh() function with the command, super.refresh(), and then calls the App’s own refresh() function, App.refresh(). It’s that function that is of particular interest to this article, but let’s continue.
A Super Refresh
It’s in the super.refresh() function where you see the setState() function being called. The refresh() function resides in the library package, mvc_pattern, which is the core component that implements in the MVC Design Pattern in Flutter. However, if you look closely at the screenshot below, you’ll realize the setState() function called also resides in this MVC library package. You can see it’s listed above the refresh() function, and it’s that function that finally calls Flutter’s own setState() function to rebuild the interface. That’s, of course, if it’s allowed to with the instance variable, _rebuildAllowed — but that’s a whole other story for another time.
An App’s Refresh
Let’s now take a quick peek at the function, App.refresh(), where the breakpoint above was set to stop code execution. It’s there you readily see the framework indeed has a reference to the ‘root’ State object called, _vw. It’s this instance variable, _vw, that references the State object called, AppView.
Of course, you don’t really need to know all this as a developer. You just need to know when you call App.refresh(), in turn, it calls its refresh() function, and you can deduce from that its own setState() function is then called — to rebuild its residing MaterialApp widget. You don’t need to know that either really. Just know the whole app is ‘refreshed’ when you call, refresh().
Let’s leave it at that. Star or Fork the WorkingMemory app and follow along if you like. I’ll be working on this app for the coming months. As things progress, I will be writing supplementary articles about specific aspects of this Flutter app and how it uses the MVC design pattern through the library package, mvc_application — all in the effort to make a maintainable yet robust Flutter app fit for release into production.
Another Bizaar Example
There’s another example I wanted to quickly review with you regarding yet another sample app called Bazaar. This app is discussed in detail in the free article, Bazaar in MVC, but here I just wanted to examine one aspect of it that also involves the ‘switching’ of its theme. In this case, it merely allows the user to change the theme from light mode to dark mode and back again.
It’s implemented a little differently and yet built on the same MVC framework, mvc_application, demonstrating you can use the framework in more than one fashion. The developer can adapt their code to specific needs —to a specific look and feel.
When switching to ‘dark mode’ and back again, literally, the Switch widget is called upon to performs the operation. Below is a screenshot of that code alongside a gif file of the app itself switching to dark mode and back again.
And so, as you see above, when a user taps on the switch, the function, setDarkMode(), is called followed by the function, refresh(). You’re familiar already with the refresh() function. Below, the first arrow points at the Preferences routine used to ‘remember’ the current mode the next time the app starts up. The function, setTheme(), is then called and assigns the appropriate ‘theme’ to the instance variable, themeData. Follow so far?
Refresh Your Theme
Like the past examples you’ve seen, a setState() function is then called which causes the MaterialApp() widget to be ‘rebuilt.’ Consequently, its named parameter, theme, is assigned the latest value. As you know, in this framework it’s the function, refresh(), that eventually calls the setState() function.
In this app, the function, onTheme(), is used to provide the latest value. Below you see when it’s called, it turns to the Controller, ThemeChanger, to get that value. Kinda different from my ‘ToDo’ app, right? It’s perfectly acceptable though — a framework has got to be able to ‘roll with it’ don’t you know.
When you take a look at that function, getTheme(), there’s the instance variable, ThemeData. See how things work here? You’ve seen that instance variable, ThemeData, before. It was assigned a new value in the function, setTheme().
Refresh The State
Again, the AppView object is the ‘root’ State object for this framework. Hence, when a refresh() function is called from anywhere in the app, the AppView will call its build() function again, which calls its MaterialApp widget again and, in this case, assigns the latest theme to the App.
Init Your Theme
Note, being an extension of the framework’s StateMVC object, the AppView object calls its initState() function and the initState() function of all the Controller objects it may have. That’s what you see in the screenshot below on the left-hand side. The class, BaraarApp (extends AppView), has the Controller, BazaarApp, and ThemeChanger, to work with. Hence, when the State object calls the ThemeChanger’s initState() function, as you see in the screenshot below on the right-hand side, that’s where this app acquires the theme to be displayed when it starts up. Easy Peasy.
Set Your Own State First
Note, calling the refresh() function means you’re calling the setState() function of the ‘current’ State object and then the setState() function of the AppView State object. It’s akin to calling the two functions in the screenshot below. Now, why would you do that you may ask? Well, I’ll demonstrate.
Let’s modify the code a bit and see what happens if you just call the ‘App Refresh’ function. It’s subtle, but you’ll notice that the switch now doesn’t change with the new mode?! You see, the ‘current screen’, if you like, has not been rebuilt with a setState() function call — only the ‘root screen’ for lack of better words.
Let’s just call the current State object’s setState() function. Of course, now you’ve got your switch back. It changes when tapped on, but that’s it. The App is not changing its colour theme at all. Looks like you need both in this particular case to give you the user experience you’re looking for.
See below, they’re both back, and it’s all back to normal. Of course, deleting those to lines now and uncommenting the function call, refresh(), would also work.
That’s it for now. You’ve got another means to access the ‘root widget’ now. Well, more importantly, to access the root widget’s State object. It that object that contains the root build() function after all.