A Better Flutter App #6
How the ‘separation of work’ is an advantage over change.
One of a series of articles detailing a comprehensive starter app. This app is generated by a template offered by the package, app_template, which uses the underlying framework, mvc_application, based on the MVC design pattern.
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
The Areas Of Responsibility
In this article, let’s examine the valued characteristic for scalability and adaptivity, and how that’s achieved with the underlying framework and its chosen design. A successful app is going to grow — in functionality and in capability. It's common for successful apps upon their debut to receive a number of requests for features in their next release. Ideally, its source code should be adaptive and allow for quick turnarounds in anticipation of such demands.
As an example, you’ll note when walking through the code for the starter app there is a distinct ‘separation of work’. The MVC design pattern delegates a separate portion of an app’s source code to three distinct roles: code for the Interface, code for the event handling, and code for the data. Doing this will allow any future requests for change in a particular aspect of the app to only affect that particular part of the app. For example, if a change request involves the complete replacement of the app’s data source, ideally, the other two portions of the app should not be affected in the least — again, making for a quick turnaround.
Let’s depict this in the Counter app as an example. Note the second gif file below to the right now has a counter of type double incrementing by its 100th decimal place. Granted, this is a really simple example, but imagine an app switching out a more complex data source — what would be the changes necessary using this design pattern? Let’s examine those changes made here.
Count On It
If we review the ‘interface’ for the counter app below, we can readily see the ‘separation of work’ implemented in this project. You’ll note the majority of the code displayed below is concerned with ‘the look’ of the counter app. It’s concerned with the placement of the screen’s components, their size, their color, etc. The actual displayed data is under the responsibility of another part of the app — the controller. You can see the occurrence of this component in the variable, con, here and there in the code below. Of course, you can guess what sort of data these entries are supplying to the interface, but you can’t readily deduce from this vantage point how that data is conceived. That’s a good thing. The practice of a consistent API and polymorphism makes this possible.
Hence, switching out the integer counter with a counter of type double did not require one bit of the code listed above to be changed — not one bit. That’s powerful. By the end of the article, we’ll see where the changes had to finally be made. Ironically, I suspect the ‘counter app’ from the original starter app would require more extensive changes.
Now, in the screenshots below, you can see the Counter app’s interface (both the StatefulWidget and the State object). It’s there, where the controller, CounterController, is introduced to the app’s interface. The controller is instantiated in the StatefulWidget to a final instance variable called, con, and actually passed to the State object (of type StateMVC) as a parameter. Note, now in that State object, it’s necessary to cast the property, controller, (of type ControllerMVC) back to CounterController so as to then access ‘the data’ it has access to.
Note, an alternate approach would be to instantiate the controller where it’s only needed — in the State object. See below. This would leave the StatefulWidget a little ‘lighter.’ In fact, the only time I have the Controller in the StatefulWidget is when I wish the StatefulWidget to have its own functions (an external API) calling, in turn, the functions in the Controller. However, that’s for another story.
You can see, highlighted in the Controller’s screenshot below, the API used by the Interface. Remember, it’s the getter, data, and the callback function, onPressed, back in the State object. Also highlighted here is the third and last component of the design pattern, the Model. The Model represents the code responsible for the app’s data. In the screenshot above, the class, CounterModel, is instantiated to the property field, _model.
When it comes down to it, the class, CounterModel, can be changed completely unbeknownst to the rest of the app. For such a simple start app, this design pattern is very much a case of overkill. However, for a typical production-worthy app with its local database, its augmented reality plugin, its Firebase Firestore, its Web Services, etc., future change requests could be easily performed with little circumstance. Look below, and you see two classes with the same name: One will increment an integer; the other increments a double. Switching between them out is a breeze.
Imagine a team of developers working together on a larger app. Possibly with separate developers delegated to each of those three aspects of the app (Data, Interface, and Logic). Give them a standard API, and any future changes are not a problem.