A Framework for Flutter Part 3
--
Using InheritWidget for efficient UI updates
In this article, Part 3 of 4, we’ll learn more about the mvc_pattern package by examining its second example app. Below is a screenshot of the StatefulWidget, MyHomePage. Now, this is very much your run-of-the-mill StatefulWidget. The one lone instance variable is set final, and the createState() function instantiates a private State object, _MyHomePageState.
It’s in the class, _MyHomePageState, we see some variation from the norm. Instead of extending the State class, it extends the class, StateMVC, and has a constructor that takes in one object simply named, Controller. If you’ve read the previous articles, you know this to be the Controller class used in the first example app as well. If you haven't read the previous articles, I’d suggest you read those first. They’ll give you a good start on what you’ll learn here.
So let’s get to it. You’ll see from the video below, the second example is simply that of a counter app again, but with a series of greetings flashing along in red. However, besides that, and unlike any other counter app you’ve ever generated, instead of ‘repainting’ the whole screen with every tap of the button, only the count value is being rebuilt leaving the rest of the interface completely untouched. With a more complex interface, updating only one small portion of the screen makes for a very efficient Flutter app. Let’s see how that’s done.
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.
Before we get too ahead of ourselves, let’s take a closer look at this example’s State class. There are some further features of the custom framework being introduced to you here. The first property highlighted below is called, rootState
, and returns the ‘first’ State object of the whole app. Consequently, the next three commands involve retrieving that State object’s controller.
With this custom framework, of course, you’ve got options in that regard. The first command utilizes the instance variable, controller, while the next two obtains the controller first by type and then by its unique identifier, keyId. Every controller and State object in this custom framework has a unique identifier. In larger projects, such an attribute can prove useful if you don’t want to share the ‘type’ of an object with other developers, but that’s for another story.
Further along now in the class, _MyHomePageState, we have the ever-important build() function. An interesting if-statement is there excluding a Padding widget from the Column widget’s List object until the property, dataObject,
contains one of those greetings I mentioned earlier: if (con.dataObject != null && con.dataObject is String).
When it does, a greeting is displayed in red. Easy peasy.
See the SetState() class below? That also comes from the custom framework, and it is the ‘widget builder’ passed to the parameter, builder, that is the only thing that runs again every time the button is tapped. It is passed a BuildContext object, context
, and curiously that ‘data object’ again, dataObject
. This may remind you of the Scoped Model framework and its ScopedModelDescendant class. With that, this does involve an InheritedWidget as well. Note, there’s a free article on InheritedWidgets if you want to read up on them. Yes, I wrote this too.
Inherit The State
Anyway, you’re not confined to the SetState class either. All that’s needed is to collect a Widget as a dependency to some InheritedWidget somewhere, and you’re then able to achieve spontaneous and meticulous ‘rebuilds’ here and there on your Flutter app’s interface — in my opinion, the only real reason you would ever want to use an InheritedWidget.
Below is a screenshot of the ‘Page 1’ State object again. I’ve commented out the setState() function as you can see and replaced it with the custom framework’s inheritBuild() function. Now, this is such a miserable example because it’s such a simple one. Thus, you will have to imagine this to be a far more complex interface listing, in fact, dozens of Widgets with their build() functions then returning even more widgets and making for a real busy screen.
Refreshing such screens would be a marked burden on resources. Back to the example — instead of ‘repainting’ the whole screen, the inheritBuild() function will just call this one build function again. The one with the lone command, inheritWidget(context)
inside and that’s it. Place that command in only the build() functions you want to execute again and again, and only those Widgets that make up your app’s screens get refreshed. Brillant! Nothing like adding a bit of efficiency to your app’s interface.
Let’s now return to the second example app. As you see in the screenshot below, the incrementCounter() function is called in the Controller object with every tap of the button. Note, there is no custom error routine assigned to this particular State object merely the parent function is called but it’s there as a reminder of the possibility with such a State object.
The next screenshot below is that of the controller object, Controller, and you can see once again inside its incrementCounter() function the function, inheritedBuild(), is being called — to ignite the builder inside the SetState class once again and display the newly incremented counter.
A Meet and Greet
There will be a new greeting every time the counter is divisible by 5. Like with any good design pattern, the Model class will take care of what ‘data’ is provided while the Controller will trigger the response when such an event occurs. And so, with every such divisible count, the function, sayHello(), in the Model class is called. See below.
A visit to the Model class shows us the code involved in supplying those various greetings. We see the function in question dedicates an index of type integer to traverse the length of a List object returning its content.
You can’t reverse the value, you can’t explicitly set it to any value. You can only retrieve the value using a particular getter, integer get counter => _counter;
, or increase the value calling the function, incrementCounter(). In other word, access and manipulation of the data inside this class are controlled. A good characteristic of any sound code.
Test the Tests
Here’s a neat little exercise. Every time you use a new Dart package, it’s downloaded to your machine, right? On my Windows machine, for example, all the Dart packages I’ve ever used are installed where I installed the Flutter SDK under the following directories, .pub-cache\hosted\pub.dartlang.org\
Create a new Flutter project, and to make things easier, simply name it, example. Next, install the custom package by introducing it into that project’s pubspec.yaml and issuing the command, flutter pub upgrade.
Note, when installing my app’s dependencies, I always use two trailing zeros,
.0.0
, to pick up any compatible changes to the code as well as any bug fixes. Admittedly, this can be a dangerous practice. You have to trust the developers of these dependencies. For this package, maybe just keep the bug fix number at zero:^8.5.0
You can trust me.
After that, seek out this Dart package under the directory, pub.dartlang.org, and copy the contents of its directory, example, back to your new project. Paste the contents to your new Flutter project overwriting its lib directory and all its files. Every published package is required to include an example app with its deployment. I would think you can do this with any and all Dart packages you’re using.
Now, your new project’s pubspec.yaml file was just overwritten, and the example app’s pubspec.yaml file was referencing the custom framework using a relative directory path. You will have to delete that line or comment it out and make some changes to reference the latest published version — as it was done in the screenshot below.
Depending on your chosen IDE, create a new ‘Flutter App’ configuration and not a ‘Flutter Test’ configuration pointing to the library file,test/widget_test.dart.
The screenshot below is that of Android Studio. Note, this all is under the assumption you’re familiar with your IDE’s debugger. Surprisingly, there are a lot of developers out there ‘programming blind’ — they don’t use a debugger. I know! How?! In my opinion, if you’re not stepping through your code, you couldn’t have any confidence in it. Anyway. That’s a whole series of articles in its own right.
A Test Run
Now, when that’s all done, run it! In the gif file below, you see the integration test is taking the example app through its paces. In the end, your IDE’s console window will display, ‘All tests passed!’ Note, however, there’s some unit testing is going on as well. By convention, you’re to place your integration tests under the directory, integration_test, and your unit tests under the directory, test. See below.
Take A Break
With that all set up, you’re free now to set ‘breakpoints’ in the code and really get an appreciation of the inner workings of the custom package, mvc_pattern. If you don’t use breakpoints much, I feel you should. Again, stepping through your code and utilizing breakpoints in my opinion will only ensure your code is the best it can be.
Anyway, the first screenshot highlights both the integration testing and the unit testing — both are high-level functions each in their own library file. The second screenshot is that of the unit testing with the particular players in this framework further broken up into separate files: one tests the ‘State Controllers’, one tests the StateMVC objects, and one tests the ‘State Listeners.’ As the name implies, Listeners are triggered by particular lifecycle events for a typical Flutter app. Unlike Controllers, they can’t disrupt or affect the lifecycle event by stopping it for example. They can only run a bit of code a developer may have placed there when particular events occur. Not used much, but maybe is a topic for another article.
The high-level function, testsStateMVC, tests the functions and features of the StateMVC class. The beginning of that function can be seen in the first screenshot below while the beginning of the function, testsController, is in the second screenshot testing the capabilities of the ControllerMVC class. Highlighted in both screenshots is the function, firstState(), from the object, tester. It’s an instance of the WidgetTester class that’s part of Flutter’s test environment, and it’s the ‘first State object’ from the app which, when using this framework, will always be of the class, AppStateMVC. Something to keep in mind.
As I’ve stated, this is a great means to understand this framework (never mind the Flutter framework itself). For example, in the first screenshot below, a breakpoint is placed in the high-level function, testsStateMVC, at line, 23. Running the configuration again, looking now at the second screenshot below, the IDE will suspend execution at that line allowing you to then step through the code and discover the property, controller, is, in fact, a getter and returns the State object’s ‘first’ State Controller. See how that works?
Made Easy
This framework is to make your life as a developer a little bit easier. The framework works like Flutter because it works with Flutter to supply the Flutter widgets and their objects you already recognize and need to provide a responsive and efficient app. Step through the test code above to understand how to best take advantage of the framework, and by virtue, take advantage of the Flutter framework itself.
Cheers.