avatarGreg Perry

Summarize

Shrine in MVC

The Shrine Sample App using the MVC design pattern

MVC

The Flutter Gallery is a repository of Flutter sample apps and snippets of code demonstrating the functions and features of the Flutter framework. It’s made available to the general public to showcase Flutter. One of those sample apps is called, Shrine, and mimics an online shopping app.

A Pattern By Design

This article will showcase an interpretation of the MVC design pattern as described in my most popular article to date, Flutter + MVC at Last! I can only suggest you read that article next to get a better understanding of the only MVC framework currently available in Flutter. Right here and now, however, I’ll demonstrate how to convert an app originally written with the Scoped Model design pattern to one that reflects the MVC design pattern.

Flutter + MVC at Last!

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.

No Moving Pictures On Social Media

Note, there are a few gif files in this article demonstrating functions and features with ‘moving pictures.’ However, I’m told viewing such files is not possible when reading this article on platforms like Instagram, Facebook, etc. You’ll see empty square spaces where the gif files should be. I wanted you to be aware of this, and recommend reading this on medium.com.

Let’s begin.

Other Stories by Greg Perry

In this article, we’ll walk through the few changes I made to replace the Scoped Model approach with that of the MVC approach. Of course, the source code for the MVC version is available to you for your review. I’ll describe the necessary changes made, as well as highlight the differences between the two approaches.

Shrine with MVC sample app

As Easy As One, Two, Three

To implement the MVC approach in the Shrine app, you do three things: Introduce ‘the View’, ‘the Controller’ and ‘the Model.’ Well, you then implement ‘the lines of communication’ between the three, but that’s done to a great extent by the MVC framework itself found in the Dart package, mvc_application. It is this framework that’s used in the conversion, and like any good framework, it already provides the many functions and features repeatedly found in a typical mobile app leaving the developer free to write the code unique to the particular app.

mvc_application

What Was Done

What I’ve done is merely comment out the ‘Scoped Model’ aspects of the original sample app, and replace them with their corresponding MVC component in many instances. A series of screenshots will follow highlighting the simple changes made in parts of the app resulting in the exact same functionality as found in the original changing in only the efficiency of its implementation — arguably.

A Model Controller

The ‘Model’ defined in the Scoped Model version of the Shrine app can be found in the file, app_state_model.dart. It is the class, AppStateModel, and to convert it to the MVC approach, I decided to simply extend it with the class, ControllerMVC. This makes it suddenly the ‘Controller’ in the MVC approach.

AppStateModel class *

But of course, this class was the Model in the original Scoped Model version, and so now it’s both the Model and the Controller. If you’re familiar with my previous articles, you’ll know a class representing both the Model and the Controller is certainly feasible, and so that’s what’s done here.

An MVC hybrid

Note, the Controller/Model class, AppStateModel, uses a factory constructor allowing for the instantiating of this class any number of times yet resulting in only one instance. It’s a choice I made. A class that would likely be ‘resource heavy’ and doesn’t make sense if instantiated more than once, (i.e. the Model class for a mobile app) makes for a good candidate for the Singleton pattern.

Refresh The Listeners

Further along in the very same class, I simply replaced the calls that would notify the Scoped Model listeners of a state change with the function call, refresh(). Both do the exact same thing. They invoke a rebuild in parts of the app by calling the setState() function of a State object somewhere deep within the framework. I’ll show you that fact later on in the article.

AppStateModel class*

What’s Your View?

To introduce the View aspect of the MVC to this simple app, I looked at the class, _ExpandingBottomSheetState, and changed its parent class from State to StateMVC. You see, in this MVC approach, doing so then makes the build() function in that State object the ‘View’ aspect of the MVC design pattern. As you see below at the second red arrow, I then associate the Controller to this View linking them together in a fashion. With that, for example, you can execute the setState() function for this State object by calling the Controller’s refresh() function. This triggers a rebuild. Clear so far?

_ExpandingBottomSheet class*

Build The Shopping Cart

This particular build() function (or View) is responsible for the shopping cart situated in the lower-right corner of the app’s screen. Therefore, this build() function is called again and again whenever a new product is selected by the user (See the gif below). In the screenshot below, I merely commented out the Scoped Model component, ScopedModelDescendant, and copied and paste the AnimatedBuilder widget in its place. You see, ScopedModelDescendant is not needed in the MVC version. The Controller/Model can readily rebuild the View on its own, in this case, with its call to the function, refresh().

_ExpandingBottomSheetState class*

Rebuild Only Here And There

However, in other cases throughout this app, it’s not that straightforward. For example, look at the screenshot of the ‘product page’ below. It lists the article of clothing and such and will list only a specific category of products when specified to do so. Look at the widget, ProductPage. It’s a StatelessWidget. There’s no State object here to call its build() function when something changes. So how did I do this to a StatelessWidget without rebuilding it all?

You’ll notice in the screenshot below, I commented out the ScopedModelDescendant class used by the Scoped Model framework and replaced it with the MVC class called, SetState. From the gif file running on the left of the screenshot, you can see it seems to work.

How I do this is that I take advantage of the fact that any widget in Flutter that, within its build() function, accesses an InheritedWidget will have that build() function called again whenever that InheritedWidget is itself rebuilt. Did you follow that? Don’t worry. You will. Regardless, this is a powerful characteristic of Flutter’s InheritedWidget.

The ProductPage widget is a Stateless widget. However, because the SetState class draws on an InheritedWidget somewhere deep in its framework, when that InheritedWidget is then called again and rebuilt, every builder function in every SetState class throughout the app is also called again and rebuilt. Hence, in the case of this Shrine sample app, parts of the app’s screen are rebuilt with every new category of products selected. That’s powerful.

As it happens, not only does the ScopedModelDescendant class pass down the data source, model, but it too uses an InheritedWidget deep in its framework to perform these very same distributed rebuilds throughout the app. Again, an attribute I find to be very powerful and advantageous. The passing of a piece of data down a widget tree, however — not so much.

ProductPage class*

You’ll notice, unlike the Scoped Model approach, since the class, AppStateModel, uses the Singleton pattern, I don’t acquire it from the widget tree, but merely instantiate it again and run the appropriate function to get the products. There’s no looking up of an ‘AppStateModel type’ in some internal Map variable called, _inheritedWidgets, somewhere in the Flutter framework. Further, there’s no (perish the thought) going back up the widget tree one ‘widget element’ at a time trying to match an instance of this class either. Either way occurs when you use the Scoped Model command, ScopedModel.of<T>(context, rebuildOnChange: onChange), in tandem with the ScopedModelDescendant class whenever it's rebuilt.

There. Done.

Well, that’s pretty much it. With those changes, I’ve reproduced the Shrine app’s with a variation of the MVC design pattern replacing the Scoped Model design pattern. I do prefer MVC over most other designs currently offered to Flutter, but let’s continue this article now with how each approach implements further corresponding requirements in this simple app. You can then further see their differences and, of course, their shared attributes.

The Scope Of The Model

As I’ve said, the Scoped Model library package, scoped_model, takes advantage of the InheritedWidget’s unique characteristics as well — with it instantiating its ScopedModelDescendant class here and there throughout an app. I would suggest, it wasn’t the passing down of data (i.e. Model class), but the ‘dispersed rebuilds’ capability that the authors of the Scoped Model library package really liked most about the InheritedWidget. I will have to ask one day.

Regardless. Below are screenshots of all the instances where the class, ScopedModelDescendant, was used in the original Shrine sample app. You’ll notice is some instances the passed down data source, model, was not even used. It was the ability instead to spontaneously rebuild that was needed in those instances. In three of the seven instances, model was not used.

_ExpandingBottomSheetState * and _ProductThumbnailRowState *
ExtraProductsNumber * and _ShoppingCartPageState *
ProductPage * and CategoryMenuPage *

Side By Side Comparison

The last instance of the ScopedModelDescendant class is found in the ProductCard class. I’ve listed it below beside its corresponding ‘MVC’ version. They both are StatelessWidget’s, but because both have accessed an InheritedWidget somewhere up the widget tree at some point, the content found in their corresponding builder functions is rebuilt whenever either version calls the setState() function of some particular State object. It is that State object, you see, that then rebuilds the InheritedWidget causing the ‘dispersed rebuilds’ elsewhere throughout the app. Magic!

ProductCard * and ProductCard ^

How MVC Does It

The six screenshots above are mirrored below but now with the MVC framework’s SetState class replacing the ScopedModelDescendant class. In the first screenshot, however, there’s no need for the SetState class, remember? That is code in the State object’s build() function (the View) and so it’s already rebuilt by the State object’s setState() function when called by its Controller — every time a product is added to the shopping cart.

However, the remaining screenshots, ‘surgically rebuilds’ parts of the Widget tree (the interface) by using the SetState class. Again, this class utilizes the same mechanism (i.e. InheritedWidget) used by the ScopedModelDescendant class. However, unlike the Scoped Model approach, there’s no parameter of type, Model, used as a data source. However, in most cases, you do notice there is a second parameter accompanying the BuildContext parameter, context. Now what is that, I wonder? I’ll introduce that parameter near the end of the article.

_ExpandingBottomSheetState ^ and _ProductThumbnailRowState ^
ExtraProductsNumber ^ and _ShoppingCartPageState ^
ProductPage ^ and CategoryMenuPage ^

How The Model Calls The State

I stated at the beginning of this article that both the MVC and Scoped Model library packages involve a State Object’s setState() function to initiate rebuilds throughout the app. Here, I want to demonstrate that. Let’s first look at the library package, scoped_model, and see how it does this.

scoped_model

The AppStateModel class, found in the file app_state_model.dart, is the Model component for both the MVC and the Scoped Model version of the sample app. In the Scoped Model version, there are a number of calls to the function, notifyListeners(). The idea is, when there’s a change in the app, you are then to notify a ‘Set of VoidCallback functions’ so the app’s interface will then reflect that change. The class, Model, in the library package, scoped_model, extends the class, Listenable, and the Listenable class maintains a list of VoidCallback listeners. Hence, so does the class, Model.

AppStateModel class#

And so, in the Model class, when its notifyListeners() function is called, you can see below a list of VoidCallback function objects are called in turn.

Model class+

The Scoped Model uses the AnimatedBuilder Widget under the hood to listen to the Model class and recreate the InheritedWidget when it changes. The widget, AnimatedBuilder, is returned by the ScopedModel<T extends Model> widget. Now, why would the Scoped Model library return a general-purpose widget traditionally used for building animations? Because it takes in a Listenable object as a parameter and eventually calls the function, setState().

ScopedModel class+

If you take a peek below, you’ll see that the widget.listenablehighlighted with the first red arrow there is the object, model, passed in the screenshot above to the named parameter, animation. Below, it’s being assigned a VoidCallback function (a listener) called, _handleChange.

Again, I suspect the Scoped Model uses the widget, AnimatedBuilder, because it also happens to have a VoidCallback function that calls what we’ve been looking for, the setState() function. The second red arrow below highlights the function, _handleChange, and there you can see what I’m talking about.

_AnimatedState class

Now, look at that! When you first started learning Flutter, you’ve likely been told to perform your changes within a setState() function. One reason for that is to make sure your changes will not produce a Future object which is not allowed when ‘rebulding.’ However, as you see above, it’s an empty function? Now, why would you do that you may ask? To call the build() function of the associated State object, that’s why. However in this case, doing so will consequently also call all the build() functions of all the descendant widgets that had accessed the Inherited widget. Got it? Don’t worry. We’ll get there.

It’s actually a common practice, even in the Flutter framework itself, to call a State Object’s setState() function with an empty anonymous function — if anything just so to ‘rebuild’ the contents of the State Object’s build() function.

Below is a screenshot of the AnimatedBuilder widget. Now its the named parameter, builder, that we want to look at. As you see below, the builder is called in this StatefulWidget’s own build() function? They’re not supposed to have build() functions?? What’s going on here!? Well, you’re right. They don’t usually have there own build() function. Besides, its State object, _AnimatedState, still calls its own build() function, but with a twist:

Widget build(BuildContext context) => widget.build(context);

AnimatedBuilder class

An interesting approach — the State object runs its widget’s own build() function. Anyway, what is the consequence of all this? Well, the thing is, calling that widget’s build() function will call the named parameter, builder:(context, _) => _InheritedModel<T>(model: model, child: child)

And as you see, the builder is an anonymous function that takes in a BuildContext object, context. It also takes in a second parameter, but it literally ignores it with the use of an underscore. However, the big thing here is that it creates an InheritedWidget. In Flutter, when a particular InheritedWidget is created again, any widgets that had access to its previous incarnation will have their own build() functions called as well. Again, that’s very advantageous!

ScopedModel class

It’s that one lone characteristic that convinced me to use the InheritedWidget in my MVC framework library package. By the way, that second parameter that was ignored was itself, a widget. It’s intended to improve animation performance. However, of course, the AnimatedBuilder widget was sought out for its other aforementioned characteristics and so the ‘child’ widget is not needed, and so it was ignored.

AnimatedBuilder class

So What’s My Hangup With Scoped Model?

I mean, it’s a neat little exercise, but I don’t need to get my data from a widget tree when I can import any number of data sources and business logic from…well…import statements! I mean, you have to use those import statements anyway to then work with that specific data source once it’s accessed from the widget tree. Why put in the extra work and use up any extra memory cycles?

Again, it seems to be a hard way to pass data. Don’t you think? Using a InhertedWidget to pass down and share its attributes to its descendant widgets (i.e. Theme.of(context)) is all good and makes sense, but passing down information other than that seems unnecessary. Particularly when it’s ‘data for the whole mobile app.’

By the way, just to accentuate further my own misgivings of the Scoped Model command, ScopedModel.of<T>(context), below is a screenshot of the static function. You can see the two options available to you when retrieving the InheritedWidget from up the widget tree.

ScopedModel class

The next screenshot presents those two functions in turn. You can see that, indeed, there’s a Map variable, _inheritedWidgets, that’s accessed to retrieve a particular InheritedWidget. It’s apparent the Flutter framework places any and all InheritedWidgets in this Map variable. Something to keep in mind.

Further below is the function, anscetorWidgetOfExactType. Note, in Flutter, every Widget is associated with an Element object. With these elements, widgets of a particular ‘branch’ of the widget tree are conceptually linked one after the other. You can see, if you happen to set the ScopedModel.of parameter, rebuildOnChange, to false, you’d be stepping back through those widgets to find your ‘inherited’ one. That could get ‘expensive’ depending on how big your app is.

The fact that every time you create or build from the class, ScopedModelDescendant, and it calls that static function. Yeah, not a fan. Particularly if the named parameter, rebuildOnChange, is set to false.

ScopedModelDescendant class

But again, I’ll happily implement one of the two functions, inheritFromWidgetOfExactType, if it’s truly the only means to have the almost magical ability to perform ‘dispersed rebuilds’ throughout my apps. If so…C’est la vie!

Now How MVC Does It

If you go take a look at the Flutter Gallery, you’ll notice the authors have chosen to introduce the ‘model’ for the Shrine app right at the beginning of the Gallery app that hosts the array of sample apps. So much for modular programming. Conceivably, every app in the Gallery can now have access to the data source found in the class, AppStateModel. In any event, it does demonstrate how the Scoped Model is initiated in an app.

_GalleryAppState class

By the way, you can see below, the parameter, model, is assigned in the State object, _GalleryAppState. Inside its initState() function, it instantiates the AppStateModel object and then uses the cascading operator, .., to load the products. It’s the State object for the very StatefulWidget passed to the runapp() function: runApp(const GalleryApp());

_GalleryAppState class

Inherit The State

Well, with regards to the MVC library package, mvc_application, I happened to set up this ‘inheriting builds capability’ in very much the same way. I chose where the MVC framework first introduces a MaterialApp widget and gets the app rolling would also be a good place for an InheritedWidget.

And so, much like the implementation of the Scoped Model in the Shrine app, the MVC framework has an InheritedWidget called, _InheritedMVC, in the build() function of the AppView class responsible for returning a MaterialApp widget. See below.

AppView class

If and when this build() function is called again to perform a rebuild, any ‘consumer’ descendant widget (i.e. SetState widgets) will also be rebuilt. Nice.

As you see in the screenshot below, the SetState class uses that InheritedWidget in its build() function. That simple act puts this widget in some List somewhere in the Flutter framework. When the InheritedWidget gets ‘dirty’ and needs to be rebuilt, so does this widget.

_inheritedMVC class

It’s A Refreshing State

By the way, when it comes to calling the setState() function in the MVC library package, it’s pretty straight forward. You can see I too use the ‘provide an empty anonymous function to the setState() function’ approach to rebuild the State object’s build() function.

StateMVC class

In the case of this MVC version, it’s the AppView class that is the State object in question. It extends the class, StateMVC. The mvc_application further extends that class and includes App.refresh() in its refresh() function.

StateMVC class

In that function, the boolean flag, _inheritedMVC.shouldUpdate, indicates if there is a SetState class being used within the app. If that’s true, the _vw.refresh() function is called, building the AppView’s build() function that contains, as you recall, that MaterialApp widget and consequently that InheritedWidget, and there you have it. Things get rebuilt around the app.

App class

Inherit From The Tree

Again, I loved the ‘surgical builds’ made possible when a widget consumes an InheritedWidget. The MVC framework’s SetState class utilizes that ability — performing rebuilds in dispersed areas. I love that! Again, however, passing down that data through the widget tree, I don’t love as much. But hey! That’s me! Who am I to deny such functionality to developers?

And so, I’ll provide such functionality as well…but with a twist.

The AppView class in the MVC framework was a perfect candidate for implementing the InheritedWidget, and as it happens, a perfect candidate for the data thereof to be passed down from. However, I wasn’t satisfied with passing down only a particular class as the Scoped Model does with its Model class.

No, sir. Any good framework should accommodate the developer as much as possible. How should I know what sort of object the developer wants to pass down?? And so, I’ll allow them to pass whatever object they want! I do this by defining a property in the AppView class with a class type of…well…Object. I even gave the property the most unimaginative name of…well…object. See below.

AppView class

In the case of the MVC version of the Shrine app, a developer who has access to the ‘AppView’ object can easily then assign that wonderfully named property, object, an instance of the ‘Model’ class, AppStateModel. Now, like the original Shrine app version, you can then access that class through a builder function — all made possible with the use of an InheritedWidget.

And so, in the ShrineApp class (that extends AppView), you see that, like the original Shrine App, I instantiate the ‘Model’ right away and made it available to the InheritedWidget. In this framework, it’s made available by simply assigning it to the property, object. By the way, like the Scoped Model version, it’s here in the initState() that I too decided to load the products.

ShrineApp class

Now why would you pass the very Model for an app that way, I don’t know, but my mantra has always been ‘always give them options’ and so there you are. With that, for example, you could simply assign it to another variable of the appropriate class type to run its functions as usual. See below.

ProductPage class

Note, however, since the parameter is of type, Object, you could pass down ‘anything’ you want frankly — not just a data source. Anything! Imagine.

It’s apparent I’m really happy with the MVC framework I’ve developed. I hope you will consider it an option for your next Flutter app.

Cheers.

TL;DR

The Structure Of MVC

As an aside, let’s quickly compare the directory structure of both versions. As with past MVC implementations, I make a point to convey the Model-View-Controller arrangement even in the very files involved. You can see below, the ‘MVC’ version has a number of directories allowing you, at a glance, to conclude what files are concerned with what aspect of the MVC design pattern.

During the conversion to MVC, I’ve made the effort to preserve the file names just so you can see the files’ source code content was actually already segregating to their ‘Model-View-Controller’ roles to some extent — even with Scoped Model. I would suggest visually ‘categorizing’ them even further like this using directories will only offer more insight to those who may have to come back to the source code down the road. It allows for better parallel development and for better long term maintainability.

*Source code as of August 22, 2019 #Source code as of April 08, 2019 ^Source code as of August 21, 2019 +Source code as of January 28, 2019

→ Other Stories by Greg Perry

DECODE Flutter on YouTube
Flutter
Mobile Development
Programming
Android App Development
iOS App Development
Recommended from ReadMedium