Enhance your Flutter development experience with Mason

Mason is changing the way we code by making our own templates called bricks. Learn how to speed up your boilerplate code and feature development.

Creating your Flutter project might be challenging. At this point, however, every developer should know that the Flutter create command will spin up a very basic starting point for your app.

On the one hand, Flutter is not opinionated, so it won’t tell you how to do things. When creating your flutter project, you pretty much have a basic bare-bones structure. Being extremely flexible can result in having so many structures that match your needs.

On the other hand, we might be in a situation where we are working on lots of different projects using Flutter, and having a more robust template could actually speed up all of our initial setups.

It’s particularly at that moment when we start thinking about templates. Should we create our template on top of the structure of a Flutter project, so we can reuse most of our code? How we can accelerate the creation of certain repeated widgets? Well, today we are going to talk about how this might be possible using the mason_cli.

Mason to the rescue 🙌

So let’s explain what’s Mason in case you don’t know it.

“A Dart template generator which helps teams generate files quickly and consistently.”

Basically, Mason will allow you to create your own templates called using the mustache template notation. This template would be language-agnostic, so even though we are going to show an example using Dart, you can always create a template for any type of programming language.

To start using the mason cli, you can visit the documentation section and download it as every dart package.

Let’s create our first brick 🧱

Once you cover the installation, we are more than ready to start creating our first brand-new brick. In this case, we are going to create something that all Flutter developers should be familiar with: a model. Any app requires the definition of a model, right? It can be a user, a post, a recipe, anything!

Writing a model can be tedious sometimes because it requires a fair amount of boilerplate code, especially if you are using json_serializable. So having a template to speed up this kind of task might be really handy for us.

So let’s start, we shall call our brick model_json.

To create a new brick, we first need to run:

mason new model_json --hooks

This will create a lot of files for us out of the box. Notice that we used the hooks flag that will also create a hooks folder. So let’s analyze what we get.

We can see that the structure is very similar to a dart package. On one hand, we have our READMEfile, LICENSE, and CHANGELOG, so you’ll be able to provide an accurate description as well as release new changes to the brick.

On the other hand, we have a folder called __brick__ in this one you’ll be able to actually create everything related to your template. This folder comes with a default example file HELLO.md that we are going to delete in order to create our own files. Finally, we also have our hooks folder, where we would be able to define scripts that can run before or after the creation of the brick. These scripts are called pre_gen.dart and post_gen.dart.

Alright so now everything is set up, what should we create then?

As we mentioned earlier we need to have a template for building any model, let’s take a look at a possible implementation of a user model:

import 'package:equatable/equatable.dart'; 
import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';

@JsonSerializable()
class User extends Equatable {

const User({ required this.id, });

factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

final String id;

Map<String, dynamic> toJson() => _$UserToJson(this);

User copyWith({ String? id, }) => User( id: id ?? this.id, );

@override List<Object?> get props => [ id, ];
}

Wait, what? I imagine that if you’ve never used json_serializable, this might sound like too much. Let’s explain it.

We have a class called User that extends from equatable, with a single property id. Moreover, we have the fromJson and toJson methods provided by json_serializable and our always useful copyWith. We also override props in order to use equatable. Certainly, there are a lot of things to remember when writing a model! But we are on our way to not worrying again about this.

How can we replace our model information to be able to create any model? Since the only thing that would change between the User model and any other model would be the name, let’s start replacing that one.

We are going to replace every single appearance of the “user” word with a built-in lambda provided by the mustache template notation we mentioned earlier. In the case of naming classes, we will use pascalCase() whereas for naming folders and files we will use snakeCase(). Once we replace everything, our template should look like this:

import 'package:equatable/equatable.dart'; 
import 'package:json_annotation/json_annotation.dart';

part '{{name.snakeCase()}}.g.dart';

@JsonSerializable() class {{name.pascalCase()}} extends Equatable {
const {{name.pascalCase()}}({ required this.id, });

factory {{name.pascalCase()}}.fromJson(Map<String, dynamic> json) =>
_${{name.pascalCase()}}FromJson(json);

final String id;

Map<String, dynamic> toJson() => _${{name.pascalCase()}}ToJson(this);

{{name.pascalCase()}} copyWith({ String? id, })
=> User( id: id ?? this.id, );

@override List<Object?> get props => [ id, ];
}

We should also replace our user.dart for {{name.snakeCase()}}.dart

And that’s it! Now that we created our first brick, we can try to create a new model. So let’s first add our brick to our mason.yaml file.
We would have to run the commands:

mason get mason make model_json

You will need to fill in the model name and the model will be created.

Here we have the result:

If you notice, all the data was replaced by your model name.

Adding hooks

Now that we finished creating our brick, let’s get back to the hooks we created earlier. As we mentioned, Mason gives you the ability to create your own scripts that will be executed before and after creating your template. A caveat here is that currently they can only be written using Dart.

To demonstrate a possible use case, we are going to update the post_gen.dart, so it can run some code after the creation of the brick. What do we need to run? As you might guess, after creating our brick and because we are using json_serializable we are not getting our auto-generated file user.g.dart.

Now, this could be part of the template itself, however, we would use this opportunity to execute a function in the post-generation script. The command we need to run is:

flutter pub run build_runner build --delete-conflicting-outputs

If we enter the post_gen.dart file, we have a simple function called run that will be executed.

import 'package:mason/mason.dart'; void run(HookContext context) { // TODO: add post-generation logic. }

The HookContext can give us access to the variables of the brick and other cool things such as the mason_logger. As we want to execute the build_runner command, we can change the run method to be like this:

import 'dart:io'; 
import 'package:mason/mason.dart';

void run(HookContext context) async {
final progress = context.logger.progress(
'Creating auto generated models using build_runner',
);
await Process.run(
'flutter', [
'pub', 'run', 'build_runner', 'build', '--delete-conflicting-outputs'
],
runInShell: true,
);
progress.complete();
}

If we create our brick again, we will have the same result, but once the brick is created we’ll notice that the script starts its execution.

Upload your brick to BrickHub 🚀

Now that we have successfully created our brick, how can we distribute it with ease? Well, you could just simply have your bricks in a central repository and share them with your teammates. However, there are more practical ways to achieve that.

BrickHub helps us discover, publish and share our bricks. Even though the platform is in closed alpha, it’s working really well. We can think of BrickHub as the pub.dev for the bricks. In fact, the experience of publishing a brick it’s pretty similar.

To publish your brick to BrickHub there are a couple of requirements.

First, you’ll need to sign up for the closed alpha and wait till you have an official account.

Secondly, sign in using the mason_cli

mason login

Lastly, publish your brick!

mason publish model_json

Now your brick will be available for everyone!

What’s next?

Well, if you haven’t tried Mason yet, I really encourage you to do so. Mason will ultimately improve your efficiency by not only leveraging all the tedious and repetitive setup, but also it’ll help you achieve that level of consistency that everyone aims to have in a project.

That being said, I invite you to think on what are the task that you often do on a daily basis and how you can automate them. Are there any of those having repetitive steps? That’s a good starting point to see if Mason can fit your needs. You can then create your own brick or explore existing bricks in BrickHub.

For this, make sure to check out the talk I presented about Mason on Flutter Global Summit ‘22!

I hope this can help you enhance your development experience.

Happy coding!

Originally published at https://somniosoftware.com.

--

--

Gianfranco Papa | Flutter & Dart
Somnio Software — Flutter Agency

CTO & Co-Founder at Somnio Software. Flutter & Dart Senior Developer and Enthusiast!