Using MVC Design Pattern in Flutter’s Stocks app example.
When you download Flutter, some example apps are downloaded along with the SDK. In the directory where you installed Flutter, you’ll find a directory called, examples. In there, you’ll find yet another directory called, stocks. The Stocks app is an example of a app written in Flutter, and in this article, we’re going to review it — but with a twist.
We’re going to review the original Stocks example app and the one that I then rewrote. I rewrote the Stocks app to incorporates the MVC design pattern and renamed it, Stocks_MVC. You see below there’s little difference in the two.
You’ll notice the ‘debug’ banner is turned off in the MVC version displayed on the right. That simply means the parameter,
debugShowCheckedModeBanner, passed to a MaterialApp widget was set to false. You, of course, have access to the Stocks_MVC repository for your own review.
Screenshots! Not Gists!
As always, I prefer using screenshots over gists to show code in my articles. I find them easier to work with, and easier to read. However, you can click/tap on them to see the code as 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 program mostly on our computers; not on our phones. For now.
The MVC Approach
As you may know, the MVC design pattern is an explicit effort to separate three aspects of a software application. In this case, Model-View-Controller describes the separation of the app’s Data (Model) from the app’s Interface (View) from the app’s Logic (Controller). If you haven’t already, there’s an article explaining my interpretation of MVC in Flutter for your reference:
The Stocks app was rewritten use the Dart package, mvc_application. This package serves as an application framework offering common functions and features found in a typical Flutter app yet utilizing the MVC design pattern.
Look and Compare
In this article, we will walk through this new Stocks app and do a code comparison of this variation with the original version included with Flutter. The goal here is to highlight the benefits gained when using the MVC design pattern. Frankly, the benefits gained when using any design pattern as opposed to simply implementing the ‘vanilla’ version represented by Flutter’s example apps.
Let’s take a look at each app’s main point of entry. The main.dart file. Note, it doesn’t have to be given that name. As long at there’s a main() function inside, the file could have been named, albuquerque.dart, for all that matters. Their main() functions are listed in screenshots below.
It is a little deceiving. The second screenshot is that of the main.dart file for the Stocks_MVC application. The first is of the original Stocks app, and it looks like there’s not much to the original’s main.dart file. However, that’s only because I took a screenshot of only its main() function — so to compare the ‘entry point’ of both versions side-by-side.
There’s a lot more to that file in fact. Since the Stocks app example is a rather simple app, the writers chose to place much of its code in the main.dart file leaving the main() function to be listed at the very end as you see on the left.
With the MVC version, however, what you see is what you get in its main.dart file. I’ve included the show clause in its import statements to give you insight as to what class is coming from where. What you see in that main.dart file is pretty much the extent of the code found in all the main.dart files in all my apps using the MVC framework, mvc_application.
Run The App
The StocksApp class is passed to the runApp() function in the original Stocks example app. The MVC version, of course, uses a framework and so extends the class, App, to then implement the createView() function.
A little more work there, but that’s quickly justified when comparing the StocksApp class in each version respectively. As you see below, much of the code in the original version has gone away in the MVC version — taken up by the underlining framework. Hence, the StocksApp class is shorter in length in the MVC version.
Comparing each of the screenshots below, you can find what they have in common. The red arrows highlight where both define their app’s title as well as their routes for example. As for the MVC version, however, the rest of the code has been distributed to other parts of the app or is delegated to the underlining MVC framework itself.
For example, you’ll note the MaterialApp widget called upon near the end in the first screenshot. The MVC version also has a MaterialApp widget, but it’s deep in the MVC framework. Up here in the second screenshot, however, you can still pass in MaterialApp parameter values. Either as parameter to the parent constructor or by overriding particular functions.
Now let’s go to the ‘home screen’ for each version. Both have a StockHome class that displays the home screen. Below is a screenshot of the StockHome class found in the MVC version. Like the original, it’s defined in the file, stock_home.dart. Now like in any design pattern, the fundamental approach of MVC is to ‘break up’ code into separate parts based on the specific areas of responsibility described earlier: Model-View-Controller.
Below is the result of such an attempt. What you see below is a file containing the ‘View’ aspect of the home screen. However, in the class’ constructor, you see the ‘Controller’ aspect of the home screen is being injected into the code. Therefore, looking closely at the build() function below, you can see it’s the ‘Controller’ that is supplying the data to be displayed by this View.
Below is a screenshot of the file above, but now listed on the right along side the stock_home.dart file from the original Stocks version. In this case, much of the code found in the original version has been split out in the MVC version into the ‘Controller’ being injected in from the constructor. Again, the pursuit here is to separate the code into its primary areas of responsibility.
It’s All By Degree
Now there’s a degree in which you can delegate here in Flutter. Flutter is made up of Widgets you see. Widgets taking in Widgets. To demonstrate, the StockHome class is listed again in two variations below. Note, both produce the exact same home screen, and yet looking closely, you can see the screenshot on the right has the Controller object doing a little more of the ‘presentation’ of data than the version displayed on the left.
You can see the right-hand version is supplying the ListTile widgets while the left-hand version appears to be more of the ‘pure’ MVC approach. It’s instead dictating what particular data item to display and what to do when events occur (i.e. a title and an event handler). In Flutter, with all its Widgets, there’s this ‘degree of delegation’ available to you if you’re a lone developer or part of a team of developers.
A Team Player
To what degree then does the ‘View’ team take up responsibility in how and what data is displayed? How much responsibility is then left to the ‘Controller’ team when it comes to how and what they display? Interesting, no?
I mean, this is a simple app — maybe too simple to convey what I’m trying to explain here, but when using Flutter, there’s the ability to set the degree in which the ‘areas of responsibility’ is broken up. I mean, look below at a third variation of the StockHome class. In this version, it would appear the ‘View’ team merely has control over the Scaffold widget while the primary components or attributes of the Scaffold widget are all supplied by the ‘Controller’ team. Regardless, this results in the exact same home screen. Do you understand what I’m getting at here?
It is a profound degree of decoupling, isn’t it. The View here supplies the means to display a Scaffold widget, but the Controller supplies the appBar, the drawer, and the body to name a few. Does the View know their contents? No. Does it have to? No. Does this allow for the data to be readily switched out without affecting this code? It most certainly does. Maybe this is the degree in which the View and the Controller should separate responsibility? Maybe.
Looking below, I’ve listed all three variations just to convey the ‘amount of code’ found in each variation of the View. In other words, it conveys how much of the responsibility the Controller takes up in what and how the data is displayed. This in not a conundrum, but an wonderful feature of MVC in Flutter! So to what degree are you then to delegate between the View and the Controller? I would say, it depends on the situation. This approach allows you options, and we developers just love options! Right?
I would suggest a large app and or a large development team would benefit with the left-most approach listed above. It allows for more refined control and a higher gradient between ‘the data’ and ‘how it’s displayed.’ The left-most approach allows for the ‘View’ team more room to play with the interface while it’s the right-most approach that allows the ‘Controller’ team more room to dictate what to display and how to display it. The right-most approach is maybe more appropriate for smaller apps and or smaller teams. Yes? No? Regardless! You’ve got options! We developers love options!
Get Your Data
Let’s see how the View’s data is conceived from its corresponding Controller. As you may have guessed looking below, the use of getters is involved here. For example, looking at the getter,
con.widget.appBar, we’ll examine what exactly is involved in bringing this expression about.
See below. The getter, widget, is retrieving a ‘library-private’ variable containing the object for the class, _Widgets. The class is named with a leading underscore, and so you know that this class also resides in the very same library file, controller/stock_home.dart.
Looking further up the class in question, we see where that ‘library-private’ variable, _widget, is being initialized. At a glance, you can see the class, StockHome, is a Controller object (extends ConrollerMVC), and its initState() function is instantiating a number of classes passing itself to their constructors.
Also note the Singleton pattern is being implemented here. The factory constructor is used to ensure only one instance of this class. After all, is there a need to have more than one instance of this Controller in this app? Nope.
In the screenshot below, the first portion of the class, _Widgets, is displayed. You can see the getter, appBar, is conceived here. Note the many getters returning the generic type, Widget. (It’s all Widgets! Allowing you to switch things out easier) You can see in the constructor below, there’s still further classes created that are then referenced in getters. All are being passed a reference to the Controller class, StockHome, listed above. The plot thickens!
The last five getters above access the properties from the class, _ListTiles. For example, the value,
con.widget.stockList, is a getter retrieving the property, stockList, from the class, _ListTiles. See below.
And so, in the second variation of the View, you now see where the text, ‘Stock List’, is coming from. It’s merely an example of how the Controller is delegated by a specific degree to supply ‘what’ and ‘how’ data is displayed.
Again, you’re free to turn the delegation up a notch! For example, the screenshot below lists the very same build() function in the screenshot above. However, it’s changed! There’s no widget displaying, ‘Stock List’, any more. That’s instead deep in the Widget,
con.widget.drawer , passed to the parameter, drawer . However, the exact same drawer is displayed as you see above. Now why would I do that? Well, I’ll tell you.
All this makes for a ‘clean’ API between the View and the Controller. With this last variation, for example, you can leave the View code alone as the app changes and evolves. Weeks, months, years later, it may be a completely different data source supplied by the backend, (through the Controller) and the View is none the wiser. All the Views knows is that there are Controller properties: scaffoldkey, appBar, floatingButton, drawer, and body. All named after the named parameters for its Scaffold widget, and that’s all it need to know. Decoupled. Versatile. Manageable. The Flutter’s own API is used as the API for the Model, for the View, and for the Controller. Nice.
Retrieve the Data
The HttpClient is used in both versions to actually retrieve the current stock prices for a finite list of assets. In fact, the MVC version uses the very same code responsible for doing so and has not changed it at all from the original. In other words, the very code below is the exact same in both versions — reflecting again the ability of the MVC design pattern to decouple its areas of responsibility. Months from now, for example, we could switch out this HttpClient for something else.
It’s All In The Delivery
Let’s compare the two versions by listing their corresponding build() functions. Both screenshots have highlighted with a little red arrow the location where the data is retrieved from the Internet. As you can see in the MVC version, the data is retrieved from the Controller.
In the MVC version, again, there is the delegation of work split up into its three areas. And so, in this case, it is the ‘app’ controller where the data comes from. With that, the three screenshots below actually make up the getter,
con.widget.marketTab. In the first screenshot, you see the ‘app’ controller is calling its own getter, stocks. This getter returns the instantiated object for the class, StockData. And again, the class StockData, is left unchanged from the original Stocks app, and so the getter, allSymbols, is used to retrieve the data.
Looking back at the original version, you can see the StockData class is indeed the same one used in the MVC version. The difference being the intermittent Controller is introduced in the MVC version allowing for further decoupling.
Develop And Compare
Let’s compare the directory structure of this MVC version of the Stocks app with the original Stocks app. Each file found in the new version continues the same function as in the original version. However, unlike the original version, they’ve been placed in the directory that best represents their function in the app and not just all stored together in one directory.
The first screenshot above is the MVC version. The common practice of ‘hiding’ Dart code in the directory, src, is utilized in this approach. Further, you can see at a glance, there are two additional directories, app and home. With these two directories, the distinction is being made for code used just for the ‘home screen’ and for code used for the whole app overall. We’ll get to the three “MVC” files, controller.dart, model.dart and view.dart, a little bit later.
Let’s open up those two directories, app and home, in the MVC version and reveal their contents. You can see there’s the additional directories, controller, model and view in those directories. Well, there’s only two in the directory, home. However, in the directory, app, these three directories categorizes and describes the role the source code in each plays in the app.
Like any good design pattern, the MVC approach strives to organize the source code into the three aforementioned areas of responsibility. You can see the implementation taken even has the directory structure arranged in such a way to readily display the three areas involved in different parts of the app.
The only ‘additional’ files made in the MVC version were the files, stocks.dart and stock_home.dart. Each are found in their respective ‘controller’ directories, and each are splitting out the code found in the original version’s main.dart and stock_home.dart files respectively. The code is organized into directories, and not all just in one directory.
Let’s go back again to the StockApp class. In both versions, it is this class that is passed to Flutter’s runApp() function. Further, in both versions, it is here where the app’s overall theme is set up. The app’s localization settings are established here, the routes to be taken are defined here, and the means of configuration the app is established here. All highlighted by arrows below.
In the original version, there is a class created to preform the configuration in the app’s settings screen. It’s highlighted by the first red arrow below and is called, StockConfiguration. The first red arrow on the second screenshot highlights the means to configure the app in the MVC version. It’s the Controller object for the whole app itself and is called, AppStocks.
How You Configure
Below, we again see the original StockApp class, but beside it, in the second screenshot, is the AppStocks class for the MVC version. It is in the AppStocks class where the initState() function is called to access the app’s data source, StockData. It is also in this class where, like the original StockApp class, you can retrieve the current theme for the app.
Note, I’ve replaced the StockConfiguration class with the use of the AppStocks class in the MVC version. I decided to keep the file, stock_types.dart, to allow for an implementation comparison, but you’ll see in the MVC version I’ve literally commented out the ‘configuration’ mechanism utilized in the original Stocks app. It’s not needed. Besides I didn’t particular like its implementation.
So when you’ve changed one of the app settings, both versions now have they’re own approach to execute and to reflect that change.
It makes sense that such events is addressed by the ‘Controller’ in the Stocks_MVC example app. Unlike the original Stocks app, instead of jury-rigging a ‘configuration’ solution with a ‘StockConfiguration’ class and ‘Updater’ class, the App’s controller is in a convenient position to work with the app’s settings.
Demonstrated in the screenshot above, the setting, stockmode, is used to convey either an optimistic or pessimistic mode (White interface for optimistic; Black interface for pessimistic). Looking below, the MVC approach in the second screenshot is pretty straightforward while the original version makes the extravagant effort to create a whole new copy of the ‘configuration’ object to reflect this new setting.
The MVC version makes for a more straightforward approach overall. Below in the first screenshot, I’ve preserved the function names called in the original Stocks example when making a change in the settings. In the original, the two interim components, widget.configuration and widget.updater, would then be called to assign that change and the setState() function is eventually called on the underlining State object to then reflect that change. In the MVC version, it’s more straight forward. We see in the second screenshot the underlining State object is called directly to assign the change (eg.
_state?.showSemanticsDebugger = v) and then reflect that change (eg.
_state?.refresh()) in the app.
In Part 2, we further examine the MVC design pattern as it is interpreted in the Flutter framework, mvc_application. We continue examining the example app, Stocks, but we will also look at two other apps written with the MVC framework. We’ll review them and compared them to the Stocks app as well as with each other.