Flutter & MVC. A Closer Look.
Examining what the mvc_pattern library package can do for you.
This is a follow-up article to the premiere publication, Flutter + MVC at Last!, that first described the library package, mvc_pattern. It’s used to implement a MVC design pattern in your mobile apps.
No Regrets. Well…
I am satisfied with my previous work like the very first article I ever wrote on Medium.com, An MVC approach to Flutter. There’s statements in there I hope stirred some insight into its readers. However, there is one particular statement I would like to ‘walk back.’
‘Frankly, you can’t get more intrinsic than passing the three MVC components to each other as parameters’ — An MVC approach to Flutter
Well, I was wrong there. Do you know what’s even more intrinsic!? An import statement, and it’s cleaner too. This is in regards to Dependency injection. You’re literally ‘injecting’ into another component of your app with just one line!
Whereas, with parameters, it’s a litter messier. I mean, you’re ‘announcing’ the injection at the point where the parameter is being taken in and then where it is being received. On top of that, you’re typing the same import statement…twice! In many cases, there’s no need. Just use one import statement when you can. When you then open up a Dart file, at a glance, you can see the dependency injection — just look at the import statements. You readily see the dependency involved in that file.
The Dart File. More than a File. It’s a Library!
Speaking of the Dart file. Unlike Java, where one ‘.java’ file must only contain one main class, and the name of that class must match the name of the file — Dart does not have that limitation. That’s huge!
“Some languages, such as Java, tie the organization of files to the organization of classes — each file may only define a single top level class. Dart does not have that limitation.” — Effective Dart: Design
A Dart file is called, a library, and I would suggest that’s because you can declare multiple classes in the same file! That’s huger! (word?) It’s perfectly permitted for a single Dart file to contain multiple classes, multiple top level variables, and multiple top level functions. That’s the hugest! That’s powerful! That can be very helpful in making industrial-strength apps.
Look again that the screenshot above. (If you’re too lazy, look below.) The Controller class is called ‘Con’ and not ‘Controller.’ It could have been called ‘Albuquerque’ for I care! The file, Controller.dart, can have any number of classes, functions and variables! You can simply leave that import statement there ‘for time immemorial’ and so over that time, switch out and change the code within the file, Controller.dart, without modifying a single character in the code below…huge.
There’s Options. Man! You’ve got options!
Like any good library package, there’s more than one way to get things done. You’ve got options. For example, right off the top of my head, I can think of three ways to implement the MVC package into the ol’ Counter app.
The App has the View
The first approach would have the class, AppMVC, define the View using its own build() function and have the Controller as a parameter to its parent’s class constructor. The file, MyHomePage.dart, is then relatively straight-forward with the Controller referenced from a static field.
Where’s the View?
The next two approaches move the View and the Controller into the file, MyHomePage.dart, leaving the ‘main’ class to extend StatelessWidget in this case.
Against The State
In this second approach, it’s the State object that’s applied against the MVC implementation. With that, it’s the State object’s build() function that now represents the View, and it is this State object that now takes in the Controller into its parent’s class constructor. The State object extends a State class called, StateMVC, found in the MVC library package, mvc_pattern.
Your Own View
In the third and last (or is it?) approach. There’s two new classes involved. This approach provides you with your own dedicated View Class. One where you implement its build() function. Again, a State class from the MVC library package is used to implement the MVC design pattern. It’s called, StateViewMVC, and its parent’s class constructor takes in this dedicated View Class I mentioned which extends the class, ViewMVC. This View Class, in turn, takes in the Controller into its parent’s class constructor.
So there you have it. Three ways to implement MVC in your app. Now you may ask, why the options? Firstly, who doesn’t love options?! Secondly, stay tuned, and I’ll explain why such options in subsequent articles to come.
You’ve Got Class!
And so, let’s now list out all the public classes that are available to you when utilizing this MVC library package:
AppMVC — Main or first class to pass to the runApp() function.
ControllerMVC — Your ‘working’ class concerned with functionality.
StatedWidget —Combines the StatefulWidget & State class into one.
StateEvents — The event handler in all the Controllers, Listeners and Views.
StateMVC — State Object seen as the 'StateView.'
StateViewMVC — State Object with Error Handler in its build() function.
ViewMVC — An explicit View Class.
State its State
We’re running in Flutter. Might as well use it. You’ll find your Controller (Class ControllerMVC), your View(Class StateMVC), and your Listeners(Class StateEvents) will have all the attributes of a State Object and more.
context — The BuildContext passed in the build() function.
mounted — A boolean value indicating if the State object is terminating or not.
widget — The associated ‘StatefulWidget’ object.
initState() — Called exactly once when the State object is first created.
dispose() — Called when the State object will never build again. Its terminated.
deactivate() — Called when the State object is removed from the Widget tree.
didChangeAccessibilityFeatures() — Called when the system changes the set of active accessibility features.
didChangeAppLifecycleState(AppLifecycleState state) — Called when the system puts the app in the background or returns the app to the foreground.
didChangeDependencies() — Called when a dependency of this State object changes.
didChangeLocale(Locale locale) — Called when the system tells the app that the user’s locale has changed.
didChangeMetrics() — Called when the application’s dimensions change. For example, when a phone is rotated.
didChangeTextScaleFactor() — Called when the platform’s text scale factor changes.
didHaveMemoryPressure() — Called when the system is running low on memory.
didUpdateWidget(StatefulWidget oldWidget) — Override this method to respond when the Widget changes (e.g., to start implicit animations).
onError(FlutterErrorDetails details) — Allows one to define their own Error Handler. The default routine is to dump the error to the console.
reassemble() — Called whenever the application is reassembled during debugging, for example during hot reload.
A Controller has those ‘event’ functions listed above and more. Below are some additional properties and functions specific to a Controller:
setState() — Notify the framework the Widget Tree is to be rebuilt.
keyId — Returns a Sting object uniquely identifying this Controller.
state — Returns the State<StatefulWidget> object associated with it.
stateMVC — A setter. Assigns a ‘StateView’ to this Controller.
stateView — Returns the StateMVC object associated with it.
addAfterListener — Add an event listener that runs ‘after’ the Controller.
addBeforeListener — Add an event listener that runs ‘before’ the Controller.
addListener — Add an event listener that runs ‘after’ the Controller.
removeListener — Removes an event listener.
The ‘StateView’ is defined by the StateMVC class. It too, of course, shares the properties and functions listed first under the ‘State its State’ heading. It is this class that is assigned the many Controllers and Listeners that may be involved in a particular application.
State Who’s Listening
With having any number of listeners, this ‘StateView’ is provided a number of functions used to add, list and remove them.
addAfterListener — Adds an event listener that runs after the Controller(s).
addBeforeListener — Adds an event listener that runs before the Controller(s).
addListener — Adds an event listener that runs after the Controller(s).
afterContains — Determines if a listener is in the ‘after’ listing.
afterList — Get a List of Listeners by a list of unique String id’s.
afterListener — Get an ‘after’ Listener by its unique String id.
beforeContains — Determines if a listener is in the ‘before’ listing.
beforeList — Get a List of Listeners by a list of unique String id’s.
beforeListener — Get an ‘before’ Listener by its unique String id.
removeListener — Remove a specific event listener from the ‘StateView.’
State Your Controllers
In this framework, of course, a View can have any number of Controllers. There are some functions in the ‘StateView’ to manage those Controllers.
add — Add a Controller to this ‘StateView.’ Returns a unique String id.
controller — A setter used to again assign a Controller to this ‘StateView.’
addList — Adds a List of Controllers to this ‘StateView.’
con — Returns a Controller by its unique String identifier.
contains — Determines if this ‘StateView’ contains a particular Controller.
controllers — Returns a Map of Controllers by their unique String identifier.
listControllers — Returns a List of Controllers by their unique String id.
remove — Removes a Controller from this ‘StateView’ by their unique id.
Two interesting attributes of the ‘StateView’ is the function, refresh, and the public String field, keyId.
refresh — Allows you to call setState() to rebuild the Widget tree.
keyId — Returns a Sting object uniquely identifying this ‘StateView.’
What’s with these String id’s?
Like any good framework, it should accommodate any number of ‘actors’ involved in a particular application: Controllers, StateViews, Listeners…and their developers!
Keep It Together but Apart
To encourage modular and parallel development, this framework allows any number of developers to contribute their own Controllers or even their own ‘StateViews’ to an application. However, it’s not good form to allow a developer access to another developer’s Controller and such. And so, from any list of Controllers that a View may have, for example, a developer can pick out their own…by using unique String identifiers.
For example, with regards to a Controller, when it’s ‘associated’ with a particular ‘StateView’, it’s assigned a unique String identifier called, keyId. A developer can easily acquire this id from their Controller and use it later on in other parts of the app (the ‘StateView’ object for example) to retrieve the Controller or Controllers they ‘own’ and not interfere with the Controllers of others.
Below, you see there are up to four ways to get at your Controller in the ‘StateView’ and in the ViewMVC class. The first approach listed in each class involves simply assigning the Controller itself to a public ‘getter’. The other three approaches, however, require your Controller’s unique String identifier, keyId.
Note, in the MyHomePageView class, you can reference the ‘StateView’ object, MyHomePageState. Conceivably then, with all your Controllers’ keys in hand, you could get at all your Controllers you’re responsible for. Grab them in a Map or in a List for example. You’ve options. All without access to the other developers’ Controllers. Nice.
Get The State
Below, to demonstrate the use of the ‘keyId’ field in the ‘StateView’ class, a static reference to of the ‘AppMVC’ object is implemented. The AppMVC class is then referenced in the Controller in this example.
Below you can see four ways to access the ‘StateView’ associated with this Controller. Of course, the first approach uses the Controller’s own property field, stateView. The next three ways, again, involves a unique String identifier. An identifier that, in this case, is assigned to the ‘StateView’ object when you implement the AppMVC class as well. Finally, note the statement, ‘var view = _sv1.view’, references the MyHomePageView class which extends the ViewMVC class.
The use of a framework will allow you to create more substantial mobile apps. It provides the means to organize your code. And with Flutter allowing a single Dart file to contain such an assortment of code, your app is then truly a composite of its amenable parts. Used wisely, it can only make your app more reliable, manageable, scalable and industrial-strength.