An MVC Approach to Flutter

Greg Perry
Follow Flutter
Published in
14 min readMay 15, 2020

An in-depth look at an MVC Project Template Sample App

This is a follow-up to the free article, Your Next Flutter Project, where I introduced a ‘Project Template’ you can use for your next Flutter project. It contains the foundation, the files, and the directories, that make up a Flutter app based on the MVC design pattern represented by the library package, mvc_application. You’re to take it and fill it up with your code for your next Flutter app. I hope to someday make such a template a ‘New Project’ option in IntelliJ and Android Studio, but that’s another story.

Your Next Flutter Project

In this article, I wish to further demonstrate the suggested implementation using the Contacts sample app currently incorporated in one of the project templates. Download this zip file and start it up. You’ll be greeted with the Contacts app. It’s suggested you use your IDE’s debugger and ‘walk through the code’ to get a better understanding of how the MVC framework works and, what’s more, how the Flutter framework itself works.

I Like Screenshots. Tap Caption For Gists.

As always, I prefer using screenshots in my articles over gists to show concepts rather than just show code. I find them easier to work with frankly. However, you can click or tap on their captions to see the code in a gist or in Github. Tap or click on the screenshots themselves to zoom in on them for a closer look.

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

Let’s begin.

Other Stories by Greg Perry

Let’s Add Contacts

When you begin using this sample app, you’ll no doubt start entering Contacts. Let’s look ‘under the hood’ to see what happens when, after you’ve typed up a particular Contact, you then press the Save button. Now in the MVC design pattern, the View aspect here, in this case, is the build() function found in the _AddContactState object listed below on the left-hand side.

At a glance, you can see the ‘Save’ event is handled, and rightly so, by a Controller object called, con. The Navigator is then called to pop back to the previous screen. On the right-hand side is a screenshot of that onPressed() function. We see the function performs some validation routines before calling its own ‘add’ routine. After that, it calls its refresh() function. A lot is going on here. Further note, the name of the function mimics the API used by Flutter itself. In other words, the function is named after the named parameter, onPressed. We’ll talk more about that soon.

Let’s be clear, that function is not found in the Controller itself. The Controller calls the function, onPressed(), but it's found in the ‘Contacts Add’ class. Highlighted below on the left-hand side is the Controller and its getter, add, accessing an instance of this other class. Below on the right-hand side is that function again clearly residing in the class called, ContactAdd.

A Count Of Three

Let’s continue and look inside the ContactAdd’s own add() function. It’s situated in its parent class, ContactEdit, and in turn, calls a function found in the app’s Model class. That makes sense. We’re saving new data and data is the responsibility of the ‘Model’ in an MVC architecture. See how there’s this separation of responsibilities in this app? Such an arrangement allows for better scalability and frankly easier maintenance of this app.

Lastly, note the class, ContactEdit, involves yet another parent class, ContactList. In fact, we see there are three distinct classes for three distinct tasks in this app: To Add a Contact, to Edit a Contact, and to List a Contact. It stands to reason then that each class has individual functions unique to the role they play. Each, however, builds on top of the other with inheritance. Interesting, no?

I wrote a free article about this approach back in January of 2019. Despite the many changes made to the MVC implementation, this article still holds up giving you further insight on the subject.

Clean up all the Flutter!

And so, the ContactEdit class has its add() function which calls the Model class and its addContact() function. The Contact app’s own Controller has no reference, no concept, of this addContact() function and that’s ok. This layer of abstraction allows for that function name to possibly change over time for example. Maybe a whole different database is manipulated by the app over time. You have that option here. Below on the left-hand side is the add() function in the class, ContactEdit. On the right-hand side, it’s the ‘Model’ that actually does the dirty work and adds the new Contact to the database.

contacts.dart and model.dart

Back in the onPressed() function in the class, ContactAdd, we see, once the new Contact is added to the database, the function finishes up by calling its refresh() function. Now note, this class takes in the ‘Contact’ controller from its constructor, and so we know right here and now it (and its parent classes) has access to the Controller — and consequently to the View (StateMVC) when performing its operations. And that’s the case here now with this ‘refresh.’

Refresh Your Contacts

When we continue to the refresh() function, we find it’s located in the class, ContactList. Makes sense — it’s to ‘refresh’ the list of Contacts. However, look at how that’s accomplished. It’s done by using functions found back in the ‘Contact’ controller. First, it retrieves a fresh list from the database (with the newly added Contact) and then redraws, or rebuilds, or refreshes the screen. Note, the screenshot below on the right-hand side reveals the Model class is again delegated to retrieve a fresh list of Contacts. Again, this makes sense. The ‘Model’ is concerned with data.

Remember now, the Controller, Contact, was taken in by the View (StateMVC) object, ContactListState (below left-hand side). And so, when the Controller calls its refresh() function, this will cause the State object’s build() function to be called again — refreshing its list of Contacts. Do you know why?

It’s because the State object’s setState() function will be called. That’s why. In Flutter to ‘refresh the current widget tree’, you call the State object’s setState() function. That function’s name, of course, implies this function is part of the ‘State Management’ process so readily recognized by developers switching to Flutter from the world of web apps. In truth, State Management is not really a paramount concern for developers any more with the Flutter framework now taking on those responsibilities. The function could have just as easily been called…well…refresh(). But I digress.

Below, in the screenshot on the left-hand side, we see the Controller’s refresh() function. You can see, in turn, it calls the View object (StateMVC) currently associated with it — calling its refresh() function. Now, you may well know that a State object doesn’t have a refresh() function in Flutter, but this is the MVC framework’s version of the State object (StateMVC). The screenshot below on the right-hand side reveals this MVC framework version of the State object. Unbeknownst to the typical developer merely using the MVC framework and not ‘looking under the hood’ as we are doing here, today, the original setState() function is eventually called, but not before going through some additional functions. Neat stuff, right?

That’s the job of a framework. In most cases, you‘re none the wiser. Let the framework do all the worrying for you. You know in Flutter, you are to use the setState() — so use it in this framework! However, it’s not actually the function you think it is. It does call the original setState() function, but not before making you sure you don't invertedly cause an error for example — like calling setState() function when you shouldn’t. A good framework should be ‘non-intrusive’ to the user, but still, do its job. Let the user get on with their work, while it does all the dirty work in the background.

By the way, the picture below presents the list of classes involved when the user presses that Save button. It goes full circle back to the State object, _AddContactState, and even back to its parent class, StateMVC, so to call the State object’s build() function again and refresh the screen with its newly entered Contact. All in an instant.

The hierarchy involved pressing Save

By the way, with this process, because the Controller is ‘refreshing’ the list of Contacts after adding a new contact, that means the View can ‘talk to’ the Controller, and the Controller can ‘talk to’ the View. That’s because the Controller, at the end of the process, is telling the View to call its setState() function to refresh the list. Perfectly fine. This configuration is consistently used in my own apps, and maybe the case in many of yours as well.

Let’s Sort This Out

Let’s take another walk through the code. This time, it’s the code involved in ‘sorting’ the list of contacts in alphabetical order. It’s hoped this will give you a further appreciation of the MVC approach used as well as an appreciation of the MVC framework’s built-in functions and features as some will be used here for this particular ability. The gif file below on the left-hand side demonstrates the sorting capability when you tap on the ‘sorting’ icon located on the app bar. The code displayed on its right highlights the very routine executed.

Setup The Sort

Let’s scroll up a little bit from the build() function displayed above and do a little review. As we now know, the list of Contacts is coming from a State object’s build() function — as prescribed by Flutter’s own framework. There’s a screenshot of it below on the left-hand side. The State object comes from the MVC framework's own class, StateMVC. As such, it’s capable of taking in a Controller object by its constructor and does so with the class, Contact.

You’ll see this pattern, again and again, when using this particular framework. In line with the MVC approach, the View component is responsible for the ‘interface’ at any given point and time when the app is running, while the Controller is responsible for responding to any and all ‘events’ caused by the system or by the user during that app’s lifecycle. What you’re seeing is one way of associating a particular Controller to a particular View. Again, in this interpretation of MVC for Flutter, the contents of the State object’s build() function is seen as the View. Peppered here and there within that function are references to the Controller ready to respond to events received by that View.

To continue, we see the initAsync() function is called in the State object’s initState() function and traditionally contains any and all ‘asynchronous’ operations that need to be completed before the View (the build() function) is ready to receive input (events) from the user or from the system. In many cases, a FutureBuilder widget in the build() function would be used instead to call the initAsync() function, but as it happens the operations in it were ‘fast enough’, and we could get away with initiating the operations from inside the initState() function. Should we impose a standard approach and always use the FutureWidget anyway? Maybe. Of course, like any good framework, you have that option.

Note, in turn, the State object’s initAsync() calls any and all associated Controllers’ own initAsync() functions. The screenshot below on the right-hand side lists the initAsync() function in the Controller, Contact.

Again, this MVC State object, when its initAsync() function is called, goes through all the initAsync() functions of its associated Controllers if any. Below is a screenshot of the StateMVC class and its initAsync() function.

mvc_pattern.dart

Your Initial Preferences

Let’s take a closer look at the Controller’s initAsync() function. We see this Controller is calling its Model’s own initAsync() function. Next, it calls the utility class, Prefs, to determine if the contacts displayed will be listed alphabetically or not when the app starts up. This is determined from a boolean value supplied by the device’s stored preferences. I felt supplying an app’s preferences is a functionality prominently required by mobile apps. Hence, such an ability is readily available to you in the MVC framework itself. The class object, Prefs, follows the Singleton approach and so you’ve access to the device’s stored preferences from anywhere in your app.

contact.dart

A Model Candidate

A quick peek at the Model class used in this app reveals that its initAsync() function is initializing a database called, Contacts. The Contacts database is opened and readied for use. Until that point, you had no idea there was a database called Contacts here. The Controller is ‘talking to’ the Model and not to the Contacts database directly. The Controller has no idea this Contacts database even exists. Again, with this layer of abstraction, you can easily upscale or readily change out a database if and when it’s necessary without affecting the rest of the app. Nice.

model.dart

Note the Controller, in turn, calls the class, ContactList, as it is referenced in the Controller’s getter called, list. Again, the Controller references three distinct classes through getters: ContactAdd, ContactEdit, and ContactList. In the Controller’s initState() function, these references are brilliantly conceived (if I do say so myself) by instantiating one class object called, ContactAdd.

contact.dart

Of course, that’s possible because each class extends the other until we come down to the class, ContactFields. The picture below depicts the inheritance involved.

Fresh Contacts

And so, back to the refreshing of the list of Contacts. When you call the refresh() function in the ‘list’ class, it will call the Controller to get the contacts once again (calling its Model) and then calls its refresh() function to rebuild the screen (calling its View) and display the new Contact entry. You can see in the getContacts() function there’s this private instance variable called, _sortedAlpha. It’s boolean, and if it’s true, the List of Contacts is sorted. Easy Peasy.

Sort This Out

And so, I think you can readily see in the screenshot above when the ‘sort’ icon on the app bar is tapped on, the Controller’s sort() function is called upon. The sorting feature is ‘toggled’ on and off with every tap. You can see how that is implemented with the use of the instance variable, _sortedAlpha. That toggled value is then immediately stored in the device’s stored preferences and the list of Contacts retrieved once again. See how that works?

ContactList extends the class, ContactFields. The ContactFields class contains the functions and features specific to those required for an app listing well…Contacts. Note, however, all the fields (private instance variables) defined in that class are of type, FieldWidgets. You’ll find this class in the MVC framework. That’s so you can try out this ‘three classes for three tasks’ approach yourself if you like.

A Class Of Item

The class, FieldWidgets, in the MVC framework extends the class, DataFieldItem. As you see, this class is only concerned with a value (whatever that is with its dynamic data type) and a label to be associated with this item. That’s it. You’re to build up the rest with those three other classes to make up the data fields for your app. You’ll find its descendent, FieldWidgets, is the real ‘busy’ one — with its close to 80 parameters and all. Again, you can read more about that in the free article I mentioned above, Clean up all the Flutter!.

field_widgets.dart

You know, I’ve got to stop it there and publish this article for now.

TL;DR

I have some more to say about the MVC framework as it relates to functions and abilities found in Scoped Model and the other architectures out there, for example, but I want to publish this now as I’ve put myself on a schedule — entrepreneurs need to do that, don’t you know. It’s part of time management and all.

However, this article is not done yet. For example, the ScopedModelDescendant class used by the Scoped Model architecture is replaced in the MVC framework with the class called, SetState.

Like the ScopedModelDescendant class, this takes advantage of the fact that whenever that InheritedWidget in Flutter is itself ‘rebuilt’, any widget with access to that InheritedWidget will also be ‘rebuilt.’ For example, in the screenshot below, the SetState widget allows the Scaffold widget to be rebuilt — even in the StatelessWidget.

contact_details.dart

I will be adding more to this TL;DR section in the next few days. Please, bookmark this article and stay tuned.

Cheers.

→ Other Stories by Greg Perry

DECODE Flutter on YouTube

--

--