Mason – less work, more programming

I’ m sure many of you may have come across a moment when you find an idea for a new project, but in order to start it you need to go through a to-do list:

  • add the essential libraries that you use in every project,

  • set all the parameters in MaterialApp,

  • copy useful widgets,

  • prepare global settings for Theme.

The list may be different for everyone, but we can rather all agree that it is a lot of work. In our projects it could take 1-2 hours – even after preparing everything in a separate folder.

Once, after creating number of projects during one week, I came up with an idea to simplify my life and check ways of automatic project generation. The first library I checked out was Stagehand. Unfortunately, it lacked a way of adding my own templates. One of the issues redirected me to another package – Mason. It allows you to define your own presets for projects. Just add everything to one folder, define the name and that’s it.

Warning
On windows devices, Dart libraries that add custom commands execute twice (As of October 2021). This is due to the loading order of the Path variable in the system. For more information see, among others, this issue – [BUG] fvm runs commands twice on [Windows] – Issue #227 – leoafarias/fvm

Using this library is extremely simple. The hardest part is defining your own templates. You need to think about the layout of the files and the variables that you need.

Using the library itself is very simple. You type the command mason make <name>and watch the files move by themselves.

This begs the question – how does it work? Well, it is not rocket science. The library uses the Mustache template system, which allows us to inject text directly into the files. It copies, moves, scans, and edits the files itself.

The library itself contains several functions and some quality of life decisions to make our work easier.
As I mentioned earlier, the Mustache template system is used here. It allows us to put “variables” into the files, which are later replaced with the ones we give it during the template generation.
//All the names of our variables are changed

//Działa to także w nazwach plików!
//Działa to także w nazwach plików!

// It also works in file names!

The code also has the ability to generate conditionally. Depending on the given variables you can decide if a given part of the code should be generated or not. This is useful when we want to create more complicated templates that have to handle many possible configurations.

One of the most important things that got to me was the fact that Mason does not require anything from the user when generating the project. At first I thought that you would have to move the template settings file every time. It turned out that all you have to do is simply go to the location of your choice and type one command. This is thanks to the global settings for the whole environment. They replaced the earlier implementation where a yaml file was defined containing information about all available bricks. This could be defined at the bottom of the folder tree. However, it was a bit clunky.

The icing on the cake is the good support for git. It allows us to define our template and store it on GitHub for example. Just use the mason get command to retrieve all the changes that have appeared on the server.

However, despite all these advantages, Mason has one, very important, drawback. You cannot use it to add partial files. Or it’s just me who hasn’t figured out how to do that. This is quite an important feature, for example, if we wanted to add a whole feature to our project. For example notifications – we would not be able to partially edit the pubspec.yaml file and we would have to remember to paste the required libraries each time. Or we would have to define the feature as part of the main template. This is not a complete deal breaker for Mason. In my opinion, however, it is the biggest problem of the current version. (It also does not look like it is going to be changed.)

I additionally believe that with access to a tool that generates most of the work for us, we get access to more freedom when creating projects. For example, I have already heard proposals to use a rather interesting storyboard library that would allow us to test widgets in a better way. This is due to the fact that we are not affected by the increased time investment in creating these features. Creating a project with built-in, complex diagnostic and testing tools takes, in theory, as long as a basic hello word.

Usage examples:

All the following examples are included in the repo on github: GitHub – Fasuh/mason_testing_ground.
Bricks are defined in the bricks folder, after calling the commands:

  • Mason get.
  • Mason list.

There should be 3 bricks displayed:

  • authorization
  • feature
  • flutter_clean_architecture

Test case 1 – Initialization of the whole project:

The most basic case – we define everything we want to have created in the project. In our repo this case is called flutter_clean_architecture.
After calling the command, we can see one thing, perhaps strange at first. Mason asks us for a variable called project_name. This is because the library has no project information. We use this name in two places:

Import links are defined after package, so every import internal to the project has a project name. You can skip this step by using relative importing

When generating pubspec.yaml we need to define the project name in it. This is rather hard to skip in a meaningful way

Other than that, the library works quite smoothly. It generates everything, when calling the flutter pub get command. We shouldn’t get any errors. We have ported our blocs, widgets, core libraries and flavors.

Test case 2 – Single module initialization

One of the most requested features in our company is the ability to generate an entire module at once. In our architecture we have a standard division into data, domain, and presentation. It takes some time to create all the files. It’s not a very complicated job, but we don’t have many people willing to do it manually. At first we used a plugin for AndroidStudio, which did it for us. However, we quickly encountered a problem when changing the version of AS, our plugin stopped working. We decided to use the newly acquired Mason in its place. In the project on github it is a brick called feature. After calling it, we get a request to fill a variable responsible for the module name. In this case we used relative imports. As you can see they work well. However, this is a matter of personal preference (I personally don’t think they look elegant).

Test case 3 – Initialization of a specific module – Authorization

Probably what interests most of us is the idea of creating an application “out of the blocks” by just calling Mason commands to add modules and just making screens. However, this is not such an obvious case. I invite you to take a look at a Brick called authorization. When generating it, we use the project name again. Other than that, we don’t require anything.

Right away we can see that we see some errors in the project. This is where the problems with the library become a little more apparent. We are not able to edit the files so as not to lose their current state, the errors that we see come from our way of error handling. We add the errors in a separate file. This is not a lot of redundant work. However, this problem can escalate very quickly.

// brick errors

Still, this is a faster way to implement popular modules like Authorization or Push Notifications.

Possible questions

  1. Is it possible to build an application “like from bricks”? – I think it is feasible in three ways:
    1. Define bricks in such a way that they do not require external dependencies (e.g. bugs). This is a fairly limiting solution. It requires a specific approach and can ultimately limit us a lot.
    2. Using an additional tool. In theory, it is possible to use an additional library (e.g. Grinder) that would invoke Mason commands, edit files, generate a model, and install libraries in pubspec.yaml. It is hard for me to determine the level of complexity of such an implementation. However, it seems to be possible.
    3. Defining modules in the main template of the whole project. We would create everything using conditional blocks. This would allow us to add everything at once. The downside of this solution is the limitation to add modules only at project creation.
      Besides these ways we can of course also just use partial generation, and manually add all the dependencies. This is in my opinion the best of both worlds.
  2. How much work can be skipped this way? – From our example, I can say that from half to a full day, depending on the project and architecture, it can cut down to as little as 30 minutes.
  3. How could Mason be used with other tools? – I mentioned above that you could create a network of tools from Grinder and Mason. This is just one example of how this library could be used to speed up our work. All thanks to the simple structure of this library.

I wanted to dedicate the last section to a light shout out to the library author. Its longevity seems very good, we get frequent updates. I really liked the situation when I asked a question in issue regarding the non-working integration with windows. Not only did I get an answer within a few hours, but on the same day the integration was reworked into a new, working one.

It seems to me that Mason may become one of Flutter developers basic tools in the future, next to FVM or build_runner. So I recommend giving it a chance and defining your own template!

This site is registered on wpml.org as a development site.