MVC in Flutter Part 3

Greg Perry
Flutter Community
Published in
17 min readAug 7, 2019

Walkthrough apps written with an MVC framework.

MVC

Examine the class below. Know that it extends the class, StateMVC, that comes from the Dart package, mvc_pattern, and itself extends the class, State. Note, this class is an abstract class, so there is some function(s) to be implemented. And finally, note that many parameters are destined for a MaterialApp Widget somewhere.

AppViewState class

This class is found in the Dart package, mvc_application, and, as it pertains to the MVC design pattern, is considered the ‘View’ aspect of the app. Looking at the first little red arrow above, you can see there’s a reference to this View’s corresponding Controller. This class is given the very descriptive name, AppViewState, while its Controller is called, AppController. Hence, they are both the ‘View’ and the ‘Controller’ for the app as a whole.

In this article, we’ll examine the Dart package, mvc_application, in some detail. To do so, we’ll walk through the two example apps, weather_cast and then contact services while continually ‘look under the hood’ at this underlining MVC software framework that was used to make them.

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. However, you can click/tap on them to get at the code in a gist or in Github if you must. Ironically, it’s better to read this article about mobile development on your computer than on your phone. Besides, we program on our computers; not on our phones. For now.

This is Part 3 of a three-part series examining an approach to organize and develop your Flutter apps following the concepts behind the MVC design pattern. The series further introduced the Dart package, mvc_application, as it is the software framework providing the generic functionality and standard approach to making a Flutter app — using MVC as a guide.

MVC in Flutter Part 3
Other Stories by Greg Perry

Let’s begin.

Now back to the class, AppViewState. It’s an abstract class, and the function, build(), is the function to be implemented. In this interpretation of the MVC design pattern, the contents of the build() function represents ‘the View’ aspect of the design pattern, MVC.

main.dart

In the example app, weather_cast, in its main.dart file (see above), the ‘View’ of the app is considered the class, WeatherApp. Now, it extends the class, AppView. Well, guess what that class extends? It extends the first class you saw above, AppViewState. The class, WeatherApp, is listed below. Look at the parameter values passed to its parent class, AppView.

WeatherApp class

In the screenshot above, we see ‘the View’ has passed in the following parameters: the title of the app, its Controller, and the home screen Widget. Note, this class has also overwritten the function, onTheme(), with what you would guess correctly is a getter from a Controller class, con.ThemeCon.theme.

Further, notice the ‘as’ clause is used because both the View class and the Controller class have the same name, WeatherApp. The clause makes the distinction allowing me to reference properties from both classes.

Let’s continue up the class hierarchy. What follows now is the AppView class. We’re going inside the very software framework. Remember, this class extends the abstract class, AppViewState. It is in this class where the MaterialApp Widget is instantiated; where a great many of those parameters are finally put to use. The first red arrow highlights the fact that the ‘debugPaint’ properties are also utilized here, allowing developers access to those visual tools while developing their app.

AppView class

Notice the second arrow above depicts the fact that if there’s no ‘App Controller’ provided, it’s created and supplied to the class, AppViewState. Below, the class continues, and you can see, the MaterialApp widget is indeed instantiated. The next red arrow directs you to the fact that the ?? operator is repeatedly used to supply a non-null parameter value if possible to the MaterialApp widget.

Finally, the last red arrow reveals that if there is no theme parameter value passed to the app, weather_cast, (which there wasn’t) then the function, onTheme(), is called, and if it’s implemented (which it was), it will provide the ‘theme’ of the app. In this case, when the example app changes its location, and consequently its weather conditions, the colour of the app’s interface will change with it because of the overridden onTheme() function. Now, if it weren’t implemented, the app itself would provide the theme with App.getThemeData.

AppView class

From The Start

In the main.dart file, we see the main() function. Of course, it is here where the Flutter app begins. This is the beginning of the underlining software framework as well. You can see the MyApp class extends the App class. The App class comes from the Dart package, mvc_application.

main.dart

Let’s look under the hood, and examine the App class. It’s not listing the whole class, but the screenshot below displays a good portion of the first half of the class with particular points of interest highlighted with arrows.

For example, the first arrow points to the abstract function, createView(). You’ve seen it before. Look above. It makes sense it’s abstract. After all, you’re to supply the ‘View’ when you start up your app. Now, the second arrow below shows this function, createView(), is then called in the function initApp().

App class

It’s the third arrow that highlights a particular point of interest. This framework utilizes a FutureBuilder Widget to ‘set up and ready’ the app. In other words, any asynchronous operations that simply have to complete before the app can proceed with its startup are implemented using the FutureBuilder Widget. Note, the parameter, future, takes in a function called, init().

The fourth arrow shows you where a little spinner appears in the center of the screen while any asynchronous operations complete. It comes about from the class, LoadingScreen. When they do complete, the StatefulWidget, _AppWidget, is then instantiated. Guess what’s its corresponding State object returns from its createState() function? It’s the class, AppView (the class WeatherApp extends). In other words, the build() function of the class, WeatherApp, originally instantiated back in the main.dart is now run.

_AppWidget class

The App class’ build() function runs again, by the way, if and when a ‘hot reload’ is issued, but not before its reassemble() function fired. You see, it’s in the reassemble() function where the property, hotLoad, is set to true, and so the createView() function is fired again in the init() function. See the flow?

How It All Begins

Note, the App class extends the class, AppMVC, and the AppMVC class extends StatefulWidget class. Remember, it is the App class that is passed to the function, runApp(). Try to follow along in the screenshots below: Know that App class’ corresponding State object class is the class, _AppState, and it’s in this class’ initState() function that calls the class App’s initApp() function. Essentially, it is in that function that gets the ball rolling with the app startup.

In the second screenshot above, the function call,widget.initApp() , takes you now to the function, initApp(), in the App class. See below. Note, in that function, there is the line, _vw.con?.initApp(). When that line fires, it takes us, in the case of the example app, weather_cast, to the initApp() function in the WeatherApp class. As it happens, in there, this Controller’s State object adds in a second Controller called, ThemeCon, to it’s State object (it’s View). Note, the Controller, ThemeCon, is involved in ‘changing the colour’ of the app when changing to a new weather forecast. Don’t worry, it’ll come to you.

What you’re seeing above, therefore, is the division between the mobile app, weather_cast, and the underlining software framwork, mvc_application. The line,_vw.con?.initApp() , represents “the View’s Controller’s initialization routine.” If your app’s Controller needs to perform some synchronous operations before the app gets going, the function, initApp(), is the place to do it.

Consistency In The Framework

If you’ve read the two previous articles, you know even the directory structure and file names conform to a standard conveying the MVC approach. Note, even the class library file, mvc_application, follows such an approach to organizing its files and directories. Like the sample app, weather_cast, the class library listed below on the right has its own “MVC” files and directories.

The API of MVC

Design patterns are about a standard way of organizing code, and it’s about the separation of work and responsibilities. However, it’s also about delivering a standard API so these separate areas of work and responsibilities can then talk to each other.

In the two examples app, when the Controller is ‘injected’ into the View you see there is a consistent approach. In both, the Controller class is instantiated as a parameter for a non-default superclass constructor. It’s then the software framework that provides an object reference of that Controller with the property, controller. It’s that object that then serves as are part of the API for the View to then communicate with its Controller. A uniformed approach. Consistency.

Weather class and ContactListPage class

In each screenshot above, you’re given an instance reference to the controller. In one example, it’s the instance variable, _weatherCon, of type WeatherCon. In the other example, it’s the instance variable, con, of type Controller. The type for the property, controller, is of type ControllerMVC of course.

Avoid ‘Controller Bloat’

A common criticism of the MVC design pattern is its propensity to create ‘Controller Bloat’ during development. More and more code going into a Controller to satisfy its role makes it, at times, harder to later maintain merely because of its shire size.

A One to Many relationship between a View and its Controllers
One View with many Controllers

Well, like everything in Life, it’s best to break things down into its manageable pieces. Don’t have it all in just one Controller. Have many Controllers associated with that View — each one dedicated to its specific role. Looking at the screenshot below, you can see the software framework allows you to ‘add’ any number of Controllers to a particular View, and then reference them in more than one fashion.

What’s Your API?

Anyway, back to those instance variables: _weatherCon and con. You are to use these instance variables to now ‘talk to’ the Controller and retrieve, as it pertains to MVC, the data to be displayed by this View. And so what would the API look like? Envision the ‘View team’ is given a list of function calls from the ‘Controller team’ to be used to access the data. Now, what would that list of function calls look like?

One option is for it to be anything you like. Dictated by the functions and features demanded of the project’s specifications, the public functions names and the name of the public properties could reflect the functionality required.

For example, in the weather_cast app, we know there is a city name to be entered, and then the current weather conditions for that city is returned. Therefore, in the build() function (i.e. in the View) you can guess there is the word, ‘city’, somewhere and likely the word, ‘location’, and of course the word, ‘weather’, probably. You’d be right. You can see below, looking at the red arrows, the View is able to talk to the Model, model.Weather weather = _weatherCon.weather, and to the Controller, _weatherCon, and so knows the public functions and getters of both the Controller and the Model to supply the data and the functionality dictated by the project’s specifications.

_WeatherState class

Flutter’s Your Guide

Personally, although I’m not yet fully committed to the approach, I’m trying to use Flutter’s own API (it’s own public function names, getters, and properties) as a guide with regards to the very names I would use for my own function names, getters, and properties. And so, in the screenshot below, looking back at the build() function, you’ll see there’s two instances where I simply mirrored the name of a widget’s named parameters to the callback functions when implementing the specified functionality. In other words, there’s an onPressed() function for the named parameter, onPressed, and there’s an onRefresh() function of the named parameter, onRefresh. Note, however, the onPressed() function still has the wherewithal to take in the parameter, city.

_WeatherState class

In the StockMVC example app, you can further see a sample of the variations you can impose. Below, is two examples. In the first screenshot, the View (the build() function) has to know the name of the particular ‘fields’ to be displayed. It doesn’t need to know the type of widgets they happen to be, but it has to know the names of the particular data items to be displayed.

The Controller references in the screenshot are all in purple. Note, in two instances where there are two red arrows, the Flutter API is merely mirrored in name by the Contoller’s getters. The second screenshot, takes it further, however, and all the names of the functions and getters used to talk to the Controller reflect Flutter’s own API.

StockHomeState class and StockHomeState class

Looking at the second screenshot, you see the ‘Flutter API as your guide’ approach is taken on fully with the View only needing to know there’s a Controller getter matching the name, in this case, of the named parameters for its Scaffold widget making the talking between these areas of responsibilities a little easier to develop and maintain.

Trace The Call

Next, is a little exercise on what paths are taken in the Contact Services app to get the data required by the View. It this particular app, the View gets the data from the Model but only by going through the Controller. Let’s see the path taken.

In the first screenshot, the getter, list, references the library-private variable containing the instantiated class, ContactList. Looking at the ContactList class in the second screenshot, we’re now in the Model portion of the app. The ContactList class extends the ContactFields class listed in the third screenshot. The last screenshot highlights all the ‘data’ references provided by the Controller in the View.

What’s Your Preference?

The example app, weather_cast, records the locations entered by the user in the app’s preferences. And so, the next time the app starts up, the last location is brought up — displaying its current weather conditions. When you use this Flutter framework, the stored preferences capability comes readily available.

Back in the home screen’s controller, WeatherCon, we see the mechanism provided to store and retrieve the ‘city’ location last entered. The red arrows highlight where the location is initially retrieved, and where the last city entered is recorded in the system’s preferences.

WeatherCon class

Use Your Library

This framework uses another library package called, Prefs, to work with the plugin involved when reading and writing to the system’s preferences. This library is initialized and later disposed of by the framework, and so you don’t have to worry about it. You merely call any of a number of static functions made available to you.

Below are the screenshots that depict the path of execution where the Prefs library is eventually initialized when any app using this software framework first starts up. The App class will instantiate the FutureBuilder widget supplying an initial data value of false, and then execute the function, init(), to return a Future object of type boolean. In the init() function, you see this View’s controller (if any) has it's own init() function called.

App class and AppViewState class and WeatherApp class and AppController class

In the example app, weather_cast, there is indeed a Controller for the View, and it’s the WeatherApp class. You can see its parent class, AppController, has its init() function first called, super.init(), and it is in this function that the Prefs library is initialized by the software framework. It then returns to the ‘Weather Cast’ Controller to set up features unique to that particular app.

Get Phone Info

In the last screenshot above, there is the static function call, DeviceInfo.init(). This supplies the software framework and subsequently any app is written with it a wealth of information regarding its underlining platform (i.e. the mobile phone). Take a look below, and you’ll see a lot of information about the phone is made available if and when you need it.

DeviceInfo class
DeviceInfo class

IIBelow it states below there’s data too, but that’s since been deprecated feature. The library package mentioned below has since been removed to reduce the size of the MVC framework. You’re free to explicitly include of course. More options now.

There’s Data Too

Included in the framework, of course, is a means to ‘talk to’ databases. Admittedly, it currently has the only SQLite in the Dart package, dbutils, but there’s room for others in the future. We’ll now look to the example app, contact services, as it involves working with a database. Note, there is an article behind a ‘paywall’ dedicated to this Dart package, SQLite in Flutter, for your review.

SQLite in Flutter

The next three screenshots below trail through the path of execution when the example app, contact services, first startup. The ‘home screen’ for this app is the class, ContactListPage.

Note, in the last screenshot, this ‘View’ is also injected with its ‘Controller.’ As it happens in this app, the Controller class is named, Controller. The red arrows highlight where the Controller is referenced. Two of which are static calls. Note, the property, controller, is the instantiated object of the class, Controller. It’s just here to demonstrate how the framework supplies such a property.

Path To The Database

Let’s look at the framework at work. The first screenshot below is the State object’s initState() function. In there, it loops through all the controllers that were added/associated with the StateMVC object that is the class, _ContactListState. The second screenshot is the Controller named…well…Controller. It had overwritten its initState() function, introducing its own init() function, one that calls the system’s preferences (either the sorted alphabetically or not), and to ‘refresh’ of the list of contacts. The init() function calls the Model’s own initState() function.

From the Controller, we’re now gone over to the Model side. Starting from the first screenshot below and on the left, the initState() function in the Model class, ContactsServices, calls its parent class’, DBInterface, own function, init(). In the second screenshot, we see the class, DBInterface. We can see its init() function contains its open() function. The function responsible for accessing the database and opening it.

List Your Contacts

Let’s now walk through the code used to list out the current contacts if any in the app. Back in that initState() function, we see after opening the database, it’s put to task to list its contents. You see below the list is ‘refreshed.’ The refresh() function is also found in the class, ContactList.

And so, in the class, ContactList, we see the function, refresh(), is called by the Controller only to, within that function, call the Controller in turn. It calls the Controller’s getContacts() function and then its rebuild() function to ‘rebuild’ the Widget tree. In truth, it just calls the State object’s setState() function.

In the Controller’s function, getContacts(), a function in the app’s Model is called. It too is called, getContacts(), and is found in the class, ContactService. Calling it returns a List of objects of class type, Contacts. Finally, if dictated by the boolean value supplied by the system’s preferences, the List of ‘Contacts’ is sorted alphabetically.

The Model for this app, ContactsService, extends a class which is part of the Dart package, dbutils, called DBInterface. And so, calling its function, getContacts(), to retrieve all the contacts stored in the database will invoke the SELECT statement, SELECT * FROM Contacts WHERE deleted = 0 .

When it makes that call, the function, rawQuery(), in the dbutils library is there to make the query. If there’s data found in the database, a List of Map objects,Map<String, dynamic> , is returned.

The App’s Ads

You may have noted the Banner Ad displayed along the bottom of the screen when running the app. That’s an Admob Ad. Well, it’s a test ad actually, but it is demonstrating the Ads library package that’s brought into use in this example app.

I wrote the Ads library package as well as, but chose not to include in the MVC framework. It is a ‘heavy’ resource. Too much to be part of a software framework, frankly, and so making it available instead as a separate Dart package would suffice for most circumstances.

WeatherApp class

You can see the Ads object is initialized in the ‘library-private’ constructor listed above. Note, the class has a factory constructor assuring the Singleton pattern produces only one instance of this ‘App Controller.’ The class will have its initState() function, and it is there where the AdMob ad is called upon to display itself as a banner on the bottom of the screen.

Watch for a new article on the first Friday of every month.

→ Other Stories by Greg Perry

DECODE Flutter on YouTube

--

--