A Flutter Web Framework
To date, Flutter runs on Windows, Mac, Fuchsia, and even Raspberry Pi. A stable release of Flutter Web has been available since March 03, 2021. It has since received updates improving performance and addressing usability issues.
With that, the transition of any Flutter app from a mobile to a browser has only gotten better. Web renderers are utilized to provide the best user experience: The CanvasKit renderer is used when it’s running on desktop browsers and the HTML renderer when browsing on a mobile phone.
Yes, Flutter Web is coming along nicely, but like any Flutter app on any platform, it could always use a little help. They all need to deliver what’s expected of a full-fledged production app, and custom frameworks do just that. Developers then need only concentrate on the app’s main function.
Now, this is not an architecture with its own way of doing things sitting on top of what is already a perfectly fine state management system that is Flutter. This is a custom framework that works like Flutter and works with Flutter following an established design pattern and at times merely bringing together all the widgets that makeup Flutter so as to run the appropriate ones — depending on the parameter values you supply. I’ll explain.
It’s a framework I use for all my mobile apps, and now, for all my apps on the Web. Let’s introduce it to you using the good ol’ counter app you’re presented with every time you create a new Flutter project. However, this one will be running on a browser with one additional feature:
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
An App Like Any Other
And so, you see the counter app works with a little Localization as well. Not only can it sense where it is running in the World and use the appropriate localized language — you can change the language with a tap! Thanks to a custom framework called, mvc_web.
Further, like any app, a Web app needs to get set up properly before a user can use it. The screenshot below is the main.dart file for the example app. You can see the general theme of the app is set up here. Further, the form of navigation to be used when going from webpage to webpage is established here as well as its Localization capabilities. Even the debug flags used during development can be set here. Like conventional Flutter, the AppMVC class is a StatefulWidget, and the AppState class is its State object. Pretty straightforward.
You’ll find the AppState constructor supplies the developer with a full array of parameter options. All to instruct how the app is to look and behave. If you’ve gotten to know Flutter, you’ll readily recognize the many parameters available to you in the AppState class. It’s all to make life a little easier for the humble developer. See below.
You simply have to use the parameters you want, and like any good framework, those chosen parameters are then implemented accordingly. This framework actually sits over yet another framework more conducive to mobile apps called, mvc_application, while this framework is more for web apps.
Back to the example app, the next two screenshots below focus on the ‘home’ page — the main webpage for the example app. In the second screenshot, you’re introduced to the framework’s ‘Web Page’ widget. It too has its many parameters, but our attention right now is on its controller, CounterAppController. That’s where the magic happens in most instances. The ‘Web Page’ widget extends Flutter’s StatefulWidget, and as such it’s kept light in code content. As you know, it’s traditionally the State object counterpart, and in this case, the aforementioned Controller that contains the majority of code for a typical Flutter app.
The class, CounterAppController, extends the class, WebPageController. It too, as part of the framework, carries a long list of parameter options. See below. On closer inspection, you’ll recognize these parameters are traditionally more associated with a Scaffold widget. Again, a custom framework should give ready access to the capabilities of its underlying framework and that‘s Flutter itself in this case.
Again, this particular part of the framework is designed to work with Web apps. And so, further along in the WebPageController class, you have the means to do just that. This is an abstract class, and to get your Web app up and running, the builder() function must be implemented. Further along, there are even more functions for your web page. You gotta love options, right? You’ll recognize some of the function names listed below and will deduce what they pertain to.
Deeper in this framework, you’ll find the anticipated Scaffold widget taking in the many parameter options. Further, it’ll supply default values where none were provided. All so you can build an interactive and comprehensive Web app in Flutter.
Back again to our example app. At first glance, you’ll note the ever-important Controller class is using a factory constructor and thus implementing the Singleton design pattern. I find the role of a Controller is best served with a single instance of that class throughout the life of the app. That’s because it's called upon to respond to various system and user events throughout the course of its lifecycle and yet is to retain an ongoing and reliable state. A factory constructor readily accomplishes this.
Next, note in the screenshot below, an Appbar is defined in the convenient onAppBar() function. However, being such a simple Appbar, it could have just as well been supplied as a parameter to the Controller’s constructor. That’s demonstrated in the second screenshot below. Gotta love options.
Looking back at the Scaffold widget deep in the framework, it’s the AppBar supplied by the parameter that takes precedence. Are you seeing the advantages of using a custom framework yet? Again, you just supply the appropriate parameter values, and the framework worries about the rest. There’s no ‘re-inventing the Wheel’ here. It’s still using Flutter. It’s just worrying about ‘how’ Flutter is being used. All for your benefit.
And now to the crux of the matter. Inside your Web app’s controller, the builder() function is to return a Widget representing your web page. It is the equivalent of the build() function in a typical State object but this function is privy to a number of operators and to functionality necessary in implementing a web page. You’ll note, however, in the screenshot below, you’ve still access to the same setState() function used in a typical build() function. And so, tapping on the FloatingActionButton will increment that counter as expected. But this time, it’s running on a browser.
Now, an alternate approach would be to have the ‘interface’ (the View) supplied by a separate class in a separate library file altogether. The example app has this also implemented for demonstration purposes in a separate Dart file called, counter_app_view.dart. The previous builder() function is commented out below to make way for this separate class.
Now which approach you use will depend on how ‘modular’ you wish an app’s components to be with high cohesion and low coupling as a common goal. In still other instances, it may come down to personal preference — like how one pronounces that fruit many consider a vegetable: “Tomato (/təˈmeɪto/); Tomato (/təˈmɑːto/).”
The CounterApp class now called is that of typical StatefulWidget. However, the custom framework supplies it with a special State object of the class, StateMVC, so a WebPageController object can then call that State object’s setState() function when necessary — for example, when an incremented counter is to be displayed on the screen. In the screenshot below, the setState() function is called in the Controller:
In fact, the Controller has access to the State object itself and all its other capabilities, but that’s for another article. Know for now that, since the Controller has a factory constructor, you’re only instantiating one instance of this class, that then has access to the appropriate State object you’re currently working with:
con = CounterAppController(this). The code below is just like the code in the now commented out builder() function, but now this code is dotted with the prefix,
con., here and there. You’ve still ready access to the Controller but now in a separate library file called, counter_app_view.dart — a Controller that continues to do its job as described in the MVC design pattern.
A screenshot of that Controller below reveals the setState() function being called in its onPressed() function. Note also in the screenshot, further following the MVC design pattern, there’s a separate class being instantiated into a variable called, model. It’s responsible for the ‘data source’ used by this app. Making such separations may be impractical for such a simple app, but it’ll become highly advantageous as your Flutter apps get more complicated. Clarifying that statement is also for another time, but if you know the role of design patterns have to play in software development, you know what I’m talking about.
Possibly, you‘re still new to Flutter and may be more comfortable with the traditional approach of calling the setState() function right inside the FloatingActionButton widget. That’s easily accomplished as well. See below.
Again, these additional capabilities when working with the custom framework’s State object will allow your app to be a little more modular. There will be more of a distinct line of communication between the main components that make up your app. In this case, between the app’s interface, the app’s event handling, and the app’s data source.
What’s Your Size?
Back to our example app. Screen size is important for a Flutter Web app. It’s important your app knows the screen size at any given moment so as to ‘draw’ the interface accordingly. Things may have to be redrawn, for example, when a mobile phone switches from portrait to landscape. As I’ve mentioned, the builder() function is privy to properties in the custom framework detailing such concerns. Using this framework, you’ll find the properties highlighted below to be very convenient. For example, your web page will have to vary in appearance depending on whether it’s viewed on a phone or on a large computer screen. The boolean property,
inSmallScreen, to the rescue.
The example app is found along with the package, mvc_web. It’s a good introduction to the custom framework, but if you want to get dirty in the details, take a look at my website, andrioussolutions.com. It too is written in Flutter — using the mvc_web package.
As you see, looking at the screenshot of the AppSate class used on my website, I’ve got a lot more web pages defined as routes in there. However, everything else is pretty much the same as in that simple example app.
Up The Bar
Its AppBar needed a little more detail as well, and so it’s defined in that convenient onAppBar() function instead as a parameter to the ‘WebPageController.’ See below. You might suspect that the Controller is also a little more complicated, but you’d be wrong surprisingly. Granted, there’s going to be a lot more coming out of its abstract builder() function, but again, the custom framework takes a lot of the work out of building my website. Nice.
Below are separate screenshots of the website’s Controller. Controllers in this custom framework behave very much like Flutter’s State objects, and even share the same functions. You’ve already seen that with the setState() function, however, the initState() function is also available and used like any other preparing things before its build() function, correction, its builder() function is called. In this case, what you see below are other ‘Web Page’ widgets being instantiated into variables to then be referenced in this ‘Web Page’ widget’s builder() function. It’s a case of ‘widgets calling widgets’ — just like in Flutter itself.
Taking a quick peek at the builder() function below, you see many other builder() functions are being called from those variables assigned above and the resulting widgets are being collected in a List object and finally displayed in a Column widget. Easy peasy. Note, there are some different function calls as well (
popup(),buildList(),coverPage()). I’ll get to some of those shortly.
Also presented in this Controller is the onBottomBar() function. This Controller has defined a ‘bottom bar’ segment that's to be displayed, well…at the bottom of the web page. Now, with the use of this custom framework, if no other bottom bar is defined, henceforth the one first defined will be used as the ‘default’ bottom bar for every web page. Unless, of course, a web page’s parameter,
hasbottombar, is set to false. Helpful, no? No need to define a common aspect of the website’s web pages over and over again.
Two screenshots of two separate web pages now follow below having the same ‘standard’ bottom bar displayed but defined in yet a completely different web page. What’s the use of a custom framework if it doesn’t reduce the amount of work a developer has to do?! Of course, the bottom bar will be presented differently if the website is being displayed on a mobile phone and not on a desktop computer. It’ll be a little smaller with a little less info.
Build or Builder
You may have been asking yourself, why I decided to use the term builder() and not build() in this custom framework when it came to making these ‘web page’ widgets— thus, keeping in line with the naming convention used by Flutter. I thought about that for a long time but decided the build() function would best serve in supplying that Scaffold widget you were introduced to earlier. See below. Besides, you could always override that function if you ever wanted to. I just can’t imagine why you would ever want to.
Build or List
Also at one point, I was debating whether developers would have to produce a List every time instead — even if only one Widget is to make up a web page. In the end, I decided to ‘let the market decide.’ There will be instances, for example, where a web page will be made up of a ‘list of Widgets’ contained in a List object. The ‘Use Case’ web page on my website is one such example and is conveyed in the screenshot below. Notice the builder() function merely returns null and the buildList() function is used instead. To ‘keep things simple’, I decided to offer two separate options: two separate functions.
I know, it means implementing an abstract function by simply returning null, but that perhaps helps in maintaining such code down the road. I mean, if another developer later sees this function is just returning null, well then that tells them the web page is being generated by buildList() function instead, right? You always have to think about ‘simplicity’ and ‘controlled redundancy’ when you offer software like this to the general public.
It’s Listed Anyway
It doesn’t really matter anyway. Whichever function you may choose, deep in the custom framework, the resulting widget or widgets are actually collected into a List object anyway,
List<Widget>? list = , to eventually be displayed as your website. See below. Again, as seemingly ‘hacky’ as it is, this does then explicitly makes a developer aware of the two options.
Currently, Flutter Web more likely ‘emulates’ rather than ‘simulates’ a webpage. There are still some things good ol’ HTML does better compared to Flutter Web. On this, you can refer to, Is the web version of Flutter ready for production? However, Flutter Web is coming along nicely, and with this package, mvc_web, you‘ll be well on your way to getting a Flutter app on the Web.