Works with and like Flutter; not despite Flutter.
This is the final article of the series demonstrating the custom package, mvc_pattern. We’ve gone right back to the beginning of the example app to show how it starts up. For example, like any Flutter app, it begins with a Dart file containing the main() function. I also name that Dart file, main, and that function is essentially the only thing found in there.
Further, as you see in the screenshot below, that file usually sits alone under the directory, lib. The rest of the app’s code is in the directory, src. By convention, any code found under the directory, src, is considered private and is not be imported by third-party Dart packages. Under that directory, the code is further categorized using additional directories. In most of my apps, code concerned the whole app is found under the directory, app, while code responsible for the ‘home’ screen is under the directory, home. This approach is certainly not mandatory when using this package — it’s simply a consistent approach I follow for most of my apps.
There’s A Structure
Under those two directories, the directory structure further conveys the source code’s areas of responsibility: any code responsible for the app’s interface is under the directory, view, any code responsible for the app’s data source is under the directory, model, and any code responsible for event handling or the app’s ‘business logic’ is found under the directory, controller.
Again, you’re free to implement your own arrangement, but the idea is to present the source code in such a manner that makes for easier development and maintainability. A developer readily knows where everything is located. Further, any other developer that may come onto the project will have, at a glance, a good idea of what is found where when it comes to the source code. With that said, let’s get back to the custom framework, mvc_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 State of Being
To run the example app, the MyApp class is passed to Flutter’s runApp() function. As you see in the first screenshot below, the MyApp class extends a class from the custom framework called, AppStatefulWidgetMVC. As you may have guessed, it’s a StatefulWidget. The second screenshot displays its accompanying State class, _MyAppState, extending another custom framework class called, AppStateMVC.
By the way, the second screenshot contains an inset of the directory structure highlighting the file containing this MyApp class. Its location makes sense, doesn’t it? After all, it’s generally concerned with the app as a whole. It has no idea what the home screen is going to display — it’s just involved in starting up the app and so it’s under the directory, app. Further, it’s primarily concerned with the app’s interface, and so it's further located under the directory, view. See how this works? Let’s continue.
A State of Your Own
Below is a quick peek at the two abstract classes, AppStatefulWidgetMVC and AppStateMVC, both extending Flutter’s StatefulWidget class and the State class respectively. Both were designed to start up a Flutter app, but they’re not essential to use this custom package! In fact, you’re free to ignore them, and instead continue to implement a ‘State Object Controller’ using the additional classes, StateMVC and ControllerMVC, accordingly.
In fact, the StatefulWidget, AppStatefulWidgetMVC, is really only used to present a bit of consistency — ensuring a State object of type, AppStateMVC, is created from its createState() function. I mean, you could readily do that with a regular StatefulWidget. However, frameworks are also used to offer some conformity — and so, any and all developers who encounter a Flutter app that uses this framework will recognize this configuration. See how that works? In truth, you could just use the AppStateMVC class. Now, why would you use that class, you ask.
The First State
Well, on closer examination of the AppStateMVC class, we see in the second screenshot, the asynchronous function, initAsync(). Remember in the first article of this series, it’s involved in preparing an app at startup. Why you would use the AppStateMVC class is not only for that but, in my opinion, because you’ll find the ‘first State object of an app’ to be a pivotal player in any Flutter app. Generally, it’s this object that will supply the overall functionality and ability of the particular app. It’s situated as the basis of what the app can do for the user. You’ll find yourself referencing this particular object a lot in the typical operations of your Flutter app. You’ll find it’s best to make it accessible throughout your app as it’s involved with the functional requirements assigned to the particular Flutter app.
Control the Function
And as you’ve come to know reading this series, I feel the best means to implement the functional requirements assigned to a particular Flutter app is to utilize those ‘State Controllers’ I’ve introduced in this framework. As it happens, the AppStateMVC class can take in any number of Controllers if need be. Below is a screenshot again of the example app demonstrating this. Again, I feel State objects should only contain code concerned with the app’s interface. It’s in the Controllers where the ‘brains of the operation’ should usually reside. And if there’s a number of operations, each could be assigned to an individual Controller making for easier development and maintainability of the code. Of course, being that the StateMVC class is its parent class, in fact, it’s the StateMVC class that is actually taking in one or more Controller. Very nice.
Further along in the MyAppState class, we see its build() function. Well no, we see its buildApp() function. Its build() function has already been implemented for another purpose —one that provides yet another reason to use the AppStateMVC class: InheritedWidget.
Again, you’re free to override the build() function if you must. However, as you see below, it’s currently implementing the ‘InheritedWidget’ as described in Part 3 of this series — where it then calls the buildApp() function. Spontaneous rebuilds of small areas of the interface here and there have proven the most efficient approach for some of my more ‘busy’ apps.
Last but not least, there’s the property, _dataObj. Inspired by the Scoped_Model framework with its Model class passed down the widget tree, this framework also allows for the property, _dataObj, to be made available throughout the app. Both this framework and the Scoped Model use the same mechanism (InheritedWidget) to perform this. However, unlike the Scoped Model with its Model object, I wanted the framework to accommodate the developer as much as possible, and so the property, _dataObj, is of type, Object, and is nullable. However, like the Scoped Model framework with its Model object available to every instance of its ScopedModelDescendant class, this object is available to every instance of the SetState() class as the parameter, dataObject. As an aside, I did write a free article comparing the two frameworks. See below.
How To Set It Up
Remember, back in the first article, I mentioned I purposely delayed the example app at startup for 10 seconds causing a circular process indicator to be displayed? It was so to simulate the ‘setting up’ of a typical app before it's ready to be used by the user. Well, you can see that 10-second delay in the middle screenshot below.
Now, it wasn’t arbitrarily dropped into the code merely for demonstrational purposes. It was placed in a pre-defined location where all such ‘set up’ code is to live when needed at startup: in a special kind of Controller with its initAsync() function. In that middle screenshot below, you’re presented with the AppController class. It not only extends the custom framework’s ControllerMVC, but it also works with the mixin, AppControllerMVC.
In the first screenshot above, you see where the AppController object is introduced to the app by passing it as a parameter to the AppStateMVC class. In the last screenshot, you can see where a FutureWidget calls the AppStateMVC State object’s initAysnc() function, which in turn, calls that controller's initAsync() function. See how this works? You can have anything in that initAsync() functions. You can have any number of Controllers with their initAsync() functions being called, in turn, before the app is ready for the user. That’s demonstrated in the screenshot of the AppStateMVC class initAysnc() function below. A For-loop calls any and all suitable Controllers in turn before the app is ready for the user.
By the way, AppController object could have just as easily been introduced in that List with those other Controllers. I simply chose to use the named parameter, controller, as in many of my apps, the named parameter, controllers, is never used. However, that doesn’t mean it won’t be needed someday by other apps making it relatively convenient to introduce any number of Controllers — each deligated to supply any number of functions and features to your Flutter app. That’s what a framework is to do for you — provide options.
Back To Zero
So the first three screens in the example app allowed you to increment the counters on the previous screens. However, there was also the added ability of ‘resetting the count’ on Page 1. I’ll now quickly explain how that was done. It was done using the fact that, in Flutter, although a State object traditionally stays in memory retaining its state, it will be disposed of and re-created when the StatefulWidget is assigned a ‘different’ key.
I suspect in your current Flutter apps, you rarely concern yourself with the Key parameter in your Widgets. You either don’t pass anything at all, or it’s a constant Key value that you use to retrieve those widgets in tests:
await tester.tap(find.byKey(const Key('Page 3')));
The first State object in the example app, _MyAppState, is presented in the first screenshot below. Look at the value assigned to the Key parameter. It’s literally a ‘unique Key value’ coming from the UniqueKey() function! This means every time the _MyAppState’s build() function is called, the StatefulWidget, Page1, is assigned a new Key, and so its accompanying State object, _Page1State, will be disposed of and re-created.
Now, take a look at the second screenshot of the State class, _Page3State. When you press that ‘New Key for Page1’ button on this third screen, you’re instructing the ‘first’ State object to call its build() function again — of course by calling its setState() function. The StatefulWidget, Page1, is assigned a new key, meaning a new _Page1State State object is created, and when a _Page1State State object is created, it always starts with a count of zero.
It’s a Choice, Not a Chore
It doesn’t impose itself on you. It works with Flutter, and it works like Flutter. This ‘State Controller’ can serve as a conduit from the conventionally private State object to the public StatefulWidget — never mind readily supplying the setState() function from outside a build() function.
I needed a framework to help build Flutter apps, but, as Dave Farley suggests, one that wouldn’t impede my code to fulfill the needs of each unique project. As it happens, I had to write one.
Again, it's the package, mvc_application, but at its core is this package, and now you know why.