Use a Framework on your Flutter apps!
I’ve been writing in Flutter for going on 8 months now. As time went on, I’ve written and set aside code that I would then use again and again to perform the ‘common operations’ found in all my Flutter apps. In other words, like any developer, I was building up my ‘toolkit’ so to allow for my next project to be done much faster and more effectively.
A Work In Progress
I’m working on sharing my ‘toolkit’. I’ve offered some of it with past contributions to Medium in fact. However, I’m now looking at collecting it all in one place. In one library package: mvc_application
Always Start with a Framework!
With Flutter, many ‘Frameworks’ were offered to help developers make mobile apps. These frameworks would include Redux, BLoC, Scoped Model, MVU, etc. I myself reintroduced the ‘grandfather of design patterns’ called, MVC, as yet another option for developers to make their mobile apps.
MVC was my chosen design pattern for building my Flutter apps. I would eventually release a Flutter library package called, mvc_pattern, providing the design pattern to developers. As I progressed in my own journey in Flutter, of course, the library package itself would evolve. As of this writing, it’s up to version 3.2.0.
You may want to read the article, Flutter + MVC at Last!, first to get some background on the approach I took to implement the MVC design pattern in Flutter. It’s been revised to convey the recent library package’s functions and features. In this article, I’ll be demonstrating mostly its implementation.
The library package attempts to offer the MVC design pattern in an intuitive fashion incorporating much of the Flutter framework itself. All in one lone Flutter Package:
Learn By Doing…or By Showing
In this article, I will be showcasing one simple app currently in its developmental stage. The Contact Service Example was first presented in the article, Clean up all the Flutter!. It incorporates much of the ‘MVC toolkit.’
Keep It Simple!
There may be times when you’re writing a small simple app. After all, a phone doesn’t have the capacity of a web server or a desktop computer…yet.
The ‘Contacts’ app is a small simple app. Above, you see Flutter’s runapp() function is accepting a ‘Widget’ called, MVC. This merely starts the app in following the ‘Model-View-Controller’ design pattern. It represents the following composition: MVC(View(Controller(Model())))
You can say it represents the ‘lines of communications’ of the three components involved. For example, you see the class, ContactsExampleApp, comes from a file named, View.dart. Coincidence? I think not!
Looking at the class, ContactsExampleApp, you see it extends the class, ViewMVC. Small apps generally will have, at most, two screens working on one data set. This View class was written for just such kind of small simple app. In this case, an app for a list of contacts — it’s going to have one ‘home’ screen, and one database. There’s not going to be too much code that makes up the app itself. It’s the class, ViewMVC, that does all the ‘ground work.’ It’s part of the mvc_application library package and is listed below.
A Lot Going On Underneath
There’s really not much to this class either. It too has a lot going on underneath. For example, you may recognize the long list of parameters are those normally assigned to the widget, MaterialApp. Most apps start by instantiating a MaterialApp, right? Well, this class does that for you. Note, the last few parameters involve the debugging your Layouts in Flutter.
And so, deep in that mvc_application package, there’s a build() function somewhere that takes in that View’s parameters and passes them on to a MaterialApp object. Here’s a snapshot of it listed below.
There’s a lot I’m skipping over regarding the framework for now just so to supply a quick overview of ‘the players’ involved in this simple app. We’ll get into the titty gritty later.
So, to review, looking back at the original main.dart file, you can see that the named parameter, home, is an instance variable of type, Widget, in the class, ViewMVC. See the two snapshots below for reference.
Note, if you don’t supply a ‘home’ widget, your app will simply convey to the user a loading screen: A circular rotating graphic in the center of the screen.
The ‘C’ in MVC
The Controller is introduced in the class, ContactListPage. Below is a snapshot of this class. There, you’ll see the Controller, Contacts, is supplied to the ‘MVC’ State object, StateMVC, which is part of the library package, mvc_pattern.
The View’s State & The Controller
Highlighted with arrows are points of interest involving the MVC design pattern. The Controller, Contacts, is passed to the class, StateMVC, so that controller can run code triggered by events in the State object’s lifecycle. The lifecycle of your app is always an important aspect to consider. Using this design pattern allows you to do so.
For clarity, I’ve placed ‘Contacts’ code in the functions initState() and dispose(), but they could have been placed instead ‘inside’ the Controller itself. Inside the Controller’s own initState() and dispose() functions. They would then be called at the line, ‘super.initState()’ and ‘super.dispose()’ respectively. To make a point, for now, they’re outside here in the _ContactListState object — the ‘View’ part of the app. Remember, in my MVC interpretation, every build() function is a State object serves as ‘the View’.
The ‘M’ in MVC
It’s in the Controller, Contacts, where we first encounter the Model aspect of this app’s design pattern. Looking below, you see the functions, Contacts.init() and Contacts.disposed(), that were called in the screenshot above. They, in turn, call the ‘static’ functions initState() and dispose() from the class, ContactsService. This class is the ‘Model’ for this app — and not because it comes from a file called, Model.dart, but because it’s the data source for this small simple app.
Everything In Its Place
By the way, remember above, I had called the Contacts.init() and Contacts.disposed() functions in the ‘View’ part of the app? Well, let’s place them now where they ‘functionally’ belong — in the Controller. They then are eventually be called, instead, by the State object’s initState() and dispose() function. Below is a screenshot of the Controller, Contacts, again.
Note, I didn’t have to keep the prefix, ‘Contacts.’, of course, since they’re defined in that library file, but I did merely ‘copy n’ paste after all. It would, however, be better design practice to remove the prefix static references. See below.
Nothing In Its Place
So having removed those function calls, I didn’t need the initState() or dispose() functions back in the original State object any more. That cleans things up a little bit. Ideally, you should just have the build() function in there, right? As well as any other code concerning the app’s interface. It’s part of the ‘View’ after all, and so that class should be just concerned with the interface.
However, I do have a function called, onError(), in there. Now what’s that all about? I’ll talk about Error Handling a little later.
What’s Your Source?
So, let’s quickly move along and look at the Model class, ContactsService. We can see the data source retrieving and saving contact information is, in fact, a SQLite database. Below is a screenshot of just the first half of that class. It extends a class from my ‘toolkit’, DBInterface, and you can see the function, onCreate(), has been implemented to create the data table, Contacts.
Further information on the library file for SQLite in Flutter can be found in this past article.
Divide and Conquer Code
Looking closely at the code so far, you see, while the ‘Controller’ controls the ‘state’ of the application during its life cycle, the View can ‘talk to’ the Controller; The Controller can ‘talk to’ the Model, but the View is unaware of the Model and vice versa. You’re free to ‘switch out’ SQLite for Firebase, for example, and the rest of the app will be none the wiser. It’s scalable.
It All Comes Back Around
Let’s again review what we’ve seen so far. The class, ContactsExampleApp, which is first introduced to you in the main.dart file extends the class, ViewMVC. The class, ViewMVC, extends the class, AppView. While the class, AppView, extends the class, StateMVC, and as you may know, the class, StateMVC, contains the ‘View.’ More specifically, it contains the build() function that represents the ‘View.’ And so, it’s all coming back around to the basic ‘MVC interpretation’ provided to Flutter using the package, mvc_pattern.
The ‘App’ in MVC
So what you’re seeing so far is what you’d normally implement when you’d utilize the ‘MVC pattern’ in your own app…but here, it’s at the ‘Application’ level. For example, the class, AppView’, is the ‘View’ for the whole application. You then go on and create your own little ‘Views’ and little ‘Controllers’ and such to represent your own screens — within that application. Get it? Let’s take a look at this ‘App View’, and what it does it.
Above is a truncated screenshot of the ‘App View.’ Again, the class, ViewMVC, extends this class, and so you see the many parameters that will eventually be passed on to the MaterialApp object. It’s an abstract class where its build() function, like its parent class, StateMVC, has to then be implemented to provide ‘the View.’ So, when you create your own screens with the class, StateMVC, you too will have to implement its build() function to provide ‘its View.’
A Controller at the App Level
Highlighted by the arrows above, you see the ‘Controller’ that’s applied in this ‘View’ object. Note, in the View object, there’s an init() function that returns a Future of type, Boolean. Inside that function, you see the Controller has an init() function that’s called as well. Same goes for its dispose() function. Look back at the class, ViewMVC, and you see the ‘App Controller’ is represented by the class, AppController. By the way, also looking back you can see where that init() function is called and why it returns a Future.
So for small simple apps, there’s an ‘App Controller’ assigned to your app to do some ‘common operations’ for you that most Flutter apps will require. The class, AppController, is a short in length and so easily listed below, but it does a couple of interesting things for you. Note, the arrows below.
So, what do you see? This class extends the class, ControllerMVC — the ‘work horse’ for the MVC design pattern library package, mvc_pattern. The arrows highlight some functionality included to the app. Again, my ‘toolkit’ is accessed, and at every Prefs reference, provides the app a means to retrieve and save its preferences; it’s application settings.
Note, the last arrow highlights the ability for the Controller to response the system events (for example, of the app is minimized and placed in the background). I’m merely call the parent class in this screenshot just to highlight the didChangeAppLifecycleState() function. I may have to dedicate a separate article on a Flutter app’s lifecycle. We’ll see.
I also noticed there’s no ‘@mustCallSuper’ annotation on the init() function. I think that’s a bug! Don’t you? I’ll look into it after. ;)
The App’s Preferences are saved and supplied by the library file, Prefs.dart, and is further described in this article.
The App’s Assets
In many instances, specific assets and images are to accompany the app when released into production. Below are two projects, for example, with such assets. Well, that static reference you see above, Assets.dispose(), implies there’s further such handy static functions in the class, Assets, to help you with…well…your assets. In fact, you’ve already seen, Assets.init().
An Error In Our Midst!
In past versions of the mvc_pattern Dart package, I admittedly went a bit overboard on the Error Handling implementation. I had error handlers in every Controller, Listener, as well as State Object when it came to using this framework. In practice, this proved to be an over zealous approach. Frankly, an impractical one in its implementation. And so, I stepped back a bit and determined ‘the best place’ to impose some error handling if any. That would be in the State object. In the class, StateMVC, specifically.
The class, StateMVC, has an error handler. It’s there for when the unexpected happens. By default, if an error does occur in the framework, the response is the old ‘red screen of death.’ However, your invited to override the error handler (the function, onError()) and implement your own response to possible errors. The State object is exposed to an number of external elements in the course of an app’s lifecycle — your invited to make it more resilient to that fact.
Developers should always use try…catch statements judiciously in their code. Of course, like documenting our code, we developers are ‘always’ on top of it when it comes to ensuring our code has sufficient ‘error handling’ capability. *nudge nudge* *wink wink*
So maybe, looking at the responsibility of error handling, you could think of it as it’s not so much your code you have to worry about: It’s the other guy’s! At that State object level, the class, StateMVC, is part of the ‘View’ in this MVC framework. It should have the means to possibly overcome any errors from its Controller(s) or its Listener(s).
It’s In The Framework
A framework provides a standard way to build a software application. Since it’s a standard way, it’s a recognized way. One that can then be known by more than one developer, and then allow more than one developer to quickly get to work on a software application. A new developer can approach the app and quickly get to work. Over time, a developer can return to the app and quickly get to work. It takes time to make software, and time is money. You need to use a framework. More specifically, your boss will want you to use a framework.
Embrace the Flutter!
By design, this MVC framework implementation embraces the Flutter’s own framework. It embraces the capabilities and nuances of the Dart programming language itself. Continuing with this small simple app, I’ll further convey ‘the way’ to build the software with this particular design pattern.
Like any standard, the framework is made up of a list of explicit rules on how the code is organized. So any developer, knowing those rules, can get to work on the code…fast. I use these rules for my own apps. And so, in my case, week, months, or even years later, when I return to a particular app, I’ll know where everything is, and I can get to work on it…fast.
“MVC” From the Start
Right from the start, there’s a standard way the code is organized. I've chosen to follow this standard when implementing my apps using the ‘MVC’ framework. It even starts with the names of the file and the directories that make up the app. Guess what the convention is used when naming the files and directories that made up the small simple app? Ok, don’t bother. See below.
You can readily see the code involving the ‘User Interface’ for this small simple app is under the ‘View’ directory. The code concerning the ‘data source’ is under the directory, Model, and the class, Contacts, that extends the class, ControllerMVC, is under the directory, Controller. Clean. Organized.
The Dart Way
Before I get further into it, the Dart programming language has its own rules to follow. For example, when running any application written in Dart, it starts with one lone Dart Library file found under the lib/ directory— a file with the .dart suffix. In that file, there must be one top-level function named, main(). This is the entry point for every Dart app. That’s the rule to follow. By the way, the file itself could have any name. It’s just by convention (a rule we’v chosen to follow) that it be named, main.dart.
Further, the convention is to now leave the main.dart file all alone and place all the rest of the code that makes up the app into a subdirectory called, src/. Code under lib/src is considered private. The idea being other packages (external programs) should never need access to or need to import from the directory, src/. However, if you want to then make some of them public, you are to ‘export’ those lib/src files from a file that’s found directly under lib/ directory. Interesting, no?
Have you been following these rules in your projects? No doubt, yes. Well, let’s now turn to some ‘MVC’ rules. Looking at the small simple app’s directory structure again, you can see I’m holding tough to the Model-View-Controller configuration with even having some directories and files named after these three components.
So looking again at the three directories below, again, you can readily see where all the code concerning the app’s ‘data source’ lives. You know where all the ‘interface stuff’ lives, and where all the code controlling the event handling for this app resides. Clear and clean.
Now how about those three files? Remember that Dart convention: If you want to make private files under the src/ directory publicly available, you’d ‘export’ those files from a file that’s found directly under the lib/ directory. Now guess what’s in those files. Or not, look below:
At a glance, you can see each individual file export files from their corresponding directory. However, why would one do this? Seems like an unnecessary extra layer of abstract, don’t it? Well, there’s a ‘meaning to the madness.’
Import Dependency Injection
Let’s look that the first screen presented to the user in this small simple app. We see the Widget, ContactListPage, and it’s passed in on the ‘home’ parameter. Looking at the second arrow below, you can also readily see that and the Widget, AddContactPage, coming from the ‘View part’ of the application.
What’s Your View
So, now looking at the class, ContactListPage, we see it’s a StatefulWidget. We also see its State object extends the class, StateMVC. Therefore, this contains an MVC implementation. You can then defer that its build() function is the ‘View’, and likely the contents of that build() function will be dotted with references to one or more Controllers. It’s the Controllers that provide the ‘data’ to display. In this case, it’s just one Controller highlighted with arrows.
Note, the first import statements injects the ‘Model’ into this class to be used by the Controller in keeping with the traditional interaction of the three MVC components.
What’s Coming From Where
Look at the import statements in the screenshot above. In practice, the ‘show’ directive is not traditionally used. For performance issues, I would encourage it be used, but regardless, while following this MVC implementation, developers could simple ‘cut n’ paste’ those three lines and get on with development in that library file. See below.
‘Show’ When Finished
When a developer is finished development, I would like to see them select the operation, ‘Add explicit ‘show’ combinator’, and finally explicitly include the classes involved in that particular library file loading just those classes involved.
Subsequent developers can, at a glance, then determine what classes are of which part of the MVC design pattern as well as readily see what classes are involved in that particular library file. Nice.
There’s More Than One Way To Skin A Controller
You may have noticed the Controller, Contacts, is mostly made up of static references. Makes it more ‘portable’ throughout the app and ensures one instance of the class’s particular properties. However, maybe that’s not the case. Maybe instance references are only available. Look below and see the class, _ContactListState, has changed. The State object provides a means, for example, to return a reference of the Controller called, controller.
This approach will then allow you to change the name of the Controller in just two places. Possibly change the name to a more generic form:
With the name of the class is changed to ‘Controller’, for example, you could switch out different Controllers throughout the lifetime of the app without disturbing the rest of the casebase. Just a thought.
A Final Approach
Possibly another approach is to define the Controller reference with ‘static final’. You then have only one name to possibly change in the future.
An Added Import
Look at the screenshot below of the library file, AddContactPage.dart. The first three lines tells it all. You can see who the players are. The first line tells you where the data’s coming from. The second line tells where the ‘interface’ comes from. In fact, if you look back a the contents of import file, view.dart, you’ll notice that this very file below with this class, AddContactPage, is also in there. It’s part of the ‘View’ after all.
The third line tells you the Controllers involved. Again, in this case, it’s one called, Controller. This standard approach with import statements gives the developer some structure to follow, some consistency…a framework.
The ListView widget above contains a list of textFormField types you may have never seen before. You can read up on them in a past article provided here.
What’s in a Name? Everything!
Notice the Controller class is now given the generic name, Controller, and not the name, Contacts. In fact, let’s take look at the ‘Controller’ class now as a lot has changed since we were first introduced to it. Do you still recognize it? Look at the getters below. You’ll notice the getters have access the ‘Model’ aspect of the application. Stands to reason, the Controller is to have access the Model.
Add a Bit of Clarity
Notice, looking at the class, AddContactPage, it involves a Controller called, Controller.add specifically. You can assume that it’s because this code involves ‘adding’ a new Contact that the Controller, Controller.add, does the adding. Looks clear enough. Or is it? Let’s ‘look under the hood’ and see.
Highlighted with arrows, you can see ‘the data’ is actually coming from another class, ContactService. The ‘View’ aspect of the application (the user interface) doesn’t know this, and it doesn’t need to know. Modular.
You can see three ‘classes’ that make up the CRUD aspect of the app, ContactAdd, ContactEdit and ContactList, all inherit one after the other. The last class, ContactFields, simply lists all the database fields involved. Below is just the first portion of that class.
The Contact is the Data
Take Advantage of The Dart Advantage!
As I’ve raved about in a past article, the ability for a developer to now have any number of variables, top-level functions and classes in any one Dart file is huge! And, unlike Java, not being restricted in the name of that same file in relation to the name of, say, the ‘main’ class in that file…is a game-changer. You can name the file anything you want. This frees the developer to organize their code to their particular needs or circumstance. In our case, to organize the Model-View-Controller components of this framework.
I’ll be working to release the mvc_application library has a public package.