Template generation with Mason : TBR Group experience

image

Andrew Vasin

image

1. Template Generation in Flutter.

Code generation allows you to speed up and make the development more convenient by generating ready-made pieces of a code.

This can be used to preset the architecture of an application, for example. If you want to use the wedge architecture pattern, then you will have to spend a lot of time creating all the folders and files.

Template generation will save you this time and enable you to jump right into the development with a quick start.

Also, template generation helps not only with the architecture setup, but also speeds up the development itself.

For example, we used template generation to automatically create repositories to save time and make developers create unit tests for their code, since our template, together with the repository, creates a template with tests for this repository in the test directory.

There are a large number of approaches to generating templates in Flutter, we chose Mason package, because it is the most popular, constantly developing and improving package, which also has its own hub containing bricks (templates) that can be used with this package.

2. About Mason package

Mason is a Flutter code generation tool that helps developers create reusable code blocks, called “bricks”, for their Flutter projects.

These bricks can contain any type of code, from UI elements to business logic, and can be easily imported into any Flutter project using the Mason command line interface (CLI).

One of the main benefits of using Mason is that it allows developers to easily share their code with others, making it easier for teams to collaborate and reuse the code.

Additionally, Mason makes it easy to create and manage code templates, which can save developers a lot of time and effort when starting new projects or adding new features to existing ones.

Using Mason and the Brickhub can provide a number of benefits for Flutter developers, including:

  • Time saving: By using reusable bricks, developers can save a lot of time and effort when starting new projects or adding new features to existing ones.
  • Improved collaboration: Mason makes it easy to share bricks with others, which can improve collaboration within a team and make it easier to reuse code.
  • Consistency: Using a common set of bricks can help to ensure that projects have a consistent look and feel, which can be especially useful for larger projects or teams.
  • Increased efficiency: By using Mason, developers can focus on more important aspects of their projects, rather than spending time writing a boilerplate code.

Overall, Mason and the Brickhub provide a powerful tool for Flutter developers looking to save time and improve their workflow. Whether you’re a seasoned developer or just starting out, Mason can help you create high-quality, reusable code for your Flutter projects.

3. How to install Mason

Official documentation recommend installing mason_cli from pub.dev

# 🎯 Activate from https://pub.dev
dart pub global activate mason_cli

Alternatively, mason_cli can be installed via homebrew

# 🍺 Install from https://brew.sh
brew tap felangel/mason
brew install mason

Once you have successfully installed the mason_cli you can verify your setup by running the mason command in your terminal. If mason_cli was installed correctly, you should see something similar to the following output:

🧱 mason • lay the foundation!
Usage: mason <command> [arguments]
Global options:
-h, --help Print this usage information.
--version Print the current version.
Available commands: ...

4. How to install Mason

We will describe how to create your own break using as example our simple_repository brick.

Use this command to create the new brick:

mason new simple_repositoty

It will create a new, custom brick template in the current working directory.

5. Detail

It should generate such file structure:

├── CHANGELOG.md
├── LICENSE
├── README.md
├── __brick__
└── brick.yaml

The brick.yaml file is a manifest which contains metadata for the current brick. The newly generated brick.yaml should look something like:

name: simple_repository
description: Brick for the simple creating repositories and tests for them.
repository: https://github.com/TBR-Group-software/simple_bricks/tree/main/bricks/simple_repository
version: 0.1.0+2
environment:
mason: ">=0.1.0-dev.26 <0.1.0"
vars:
repository_name:
type: string
description: Repository Name
default: Dash
prompt: What is your repository name?

The __brick__ directory contains the template for your brick. Any files, directories within the __brick__ will be generated when the brick is used via mason make.

In the example brick, our __brick__ directory contains a such files structure:

├── __brick__
│ ├── lib
│ │ ├── {{repository_name.snakeCase()}}
│ │ │ ├── {{repository_name.snakeCase()}}_repository
│ │ │ │ └──{{repository_name.snakeCase()}}
_repository.dart
│ │ │ └──api_{{repository_name.snakeCase()}}
_repository
│ │ │ └── api_{{repository_name.snakeCase()}}
_repository
│ │ └── {{repository_name.snakeCase()}}.dart
│ ├── test
│ │ └── repositories
│ │ └── {{repository_name.snakeCase()}}
_repository_test.dart
└── └──

Templates currently support only Mustache syntax, we will create our template using it.

Mason supports a handful of built-in lambdas that can help with customizing generated code:

NameExampleShorthand SyntaxFull Syntax
camelCasehelloWorld{{variable.camel
Case()}}
{{#camelCase}}{{varia
ble}}{{/camelCase}}
constantCaseHELLO_WORLD{{variable.constant
Case()}}
{{#constantCase}}{{vari
able}}{{/constantCase}}
dotCasehello.world{{variable.dot
Case()}}
{{#dotCase}}{{variable}}
{{/dotCase}}
headerCaseHello-World{{variable.header
Case()}}
{{#headerCase}}{{vari
able}}
{{/headerCase}}
lowerCasehello world{{variable.header
Case()}}
{{#lowerCase}}{{variable}}
{{/lowerCase}}
mustacheCase{{ Hello World }}{{variable.mustache
Case()}}
{{#mustacheCase}}{{variable}}{{/mustacheCase}}
pascalCaseHelloWorld{{variable.pascal
Case()}}
{{#pascalCase}}{{variable}}
{{/pascalCase}}
paramCasehello-world{{variable.param
Case()}}
{{#paramCase}}
{{variable}}{{/param
Case}}
pathCasehello/world{{variable.pathCase()
}}
{{#pathCase}}
{{variable}}{{/path
Case}}
sentenceCaseHello world{{variable.sentence
Case()}}
{{#sentenceCase}}{{variable}}{{/sentenceCase}}
snakeCasehello_world{{variable.snake
Case()}}
{{#snakeCase}}
{{variable}}
{{/snakeCase}}
titleCaseHello World{{variable.titleCase()}}{{#titleCase}}
{{variable}}{{/titleCase}}
upper CaseHELLO WORLD{{variable.upper
Case()}}
{{#upperCase}}
{{variable}}{{/upperCase}}

{{repository_name.snakeCase()}}_repository.dart file:

abstract class {{repository_name.pascalCase()}}Repository {}

{{repository_name.snakeCase()}}_repository.dart file:

/// {@template api_{{repository_name.snakeCase()}}repository} /// [{{repository_name.pascalCase()}}Repository] /// {@endtemplate} 
class Api{{repository_name.pascalCase()}}Repository extends {{repository_name.pascalCase()}}Repository { /// {@macro api{{repository_name.snakeCase()}}_repository} 
}

{{repository_name.snakeCase()}}.dart file:

export 
'api_{{repository_name.snakeCase()}}_repository/api_{{repository_name.snakeCase()}}_repository.dart';
export
'{{repository_name.snakeCase()}}_repository/{{repository_name.snakeCase()}}_repository.dart';

{{repository_name.snakeCase()}}_repository_test.dart file:

import 'package:flutter_test/flutter_test.dart';

void main() {
group("Testing {{repository_name.pascalCase()}} Repository", () {
//TODO: implement the tests

setUp(() {});
test("Test method", () {});
});
}

6. How to publish brick to the Brickhub

To publish a brick to the Brickhub using Mason, you will first need to sign up for an account on the Brickhub website.

To do this, you will need to request access to the site, and then follow the instructions in the email invite to sign up and verify your email.

Once you have an account, you can log in to the Brickhub using the mason login command.

This will prompt you to enter your email and password, and once you have successfully logged in, you will be ready to publish your brick. To publish a brick, you will use the mason publish command, followed by the –directory option and the path to your brick’s directory.

For example:

mason publish --directory ./my_brick

This will start the process of publishing your brick to the Brickhub.
You will be prompted to confirm that you want to publish the brick, and once you confirm, the brick will be bundled and published to the Brickhub.

It’s important to note that you will need to be logged in to an account in order to publish a brick to the Brickhub.

Overall, publishing a brick to the Brickhub is a straightforward process that can be accomplished quickly and easily using the Mason CLI.
By making your bricks available on the Brickhub, you can share them with other Flutter developers and save time and effort when working on your projects.

7. Hooks

Mason supports Custom Script Execution so called hooks.

These hooks are defined in the application’s configuration and can be executed before or after certain events occur, such as the generation of code or the rendering of templates.

Currently, Mason only supports hooks written in the Dart programming language.

These hooks are defined in the hooks directory at the root of the brick and must contain a run method that accepts a HookContext from the package:mason/mason.dart library

There are two types of hooks available in Mason: pre_gen and post_gen, which are executed immediately before and after the generation step, respectively.

These hooks can be used to perform tasks such as logging, modifying the brick variables, or interacting with the logger.

For example, hooks for running dart formatting.

Future<void> _runDartFormat(HookContext context) async {
final formatProgress = 
context.logger.progress('Running "dart format ."');
await Process.run('dart', ['format', '.']);
formatProgress.complete();
}

Future<void> _runDartFix(HookContext context) async {
final formatProgress = 
context.logger.progress('Running "dart fix --apply"');
await Process.run('dart', ['fix', '--apply']);
formatProgress.complete();
}

8. What benefits we have got

Using Mason templates has provided a number of benefits for our development process, including:

  • Faster development: By generating ready-made pieces of code, we were able to speed up development and jump right into creating new features or working on existing ones. This saved us a lot of time and allowed us to focus on more important tasks.
  • More structured code: Using Mason templates helped us create a more structured and organized codebase, which made it easier to maintain and scale our projects. We were able to create a consistent structure for our code, which made it easier for new developers to understand and work with.
  • Forced developers to write unit tests: One of the benefits of using Mason templates is that they can be customized to include certain requirements or best practices. In our case, we used templates to force developers to write unit tests for their code, which helped us to ensure that our code was well-tested and of high quality.

Overall, using Mason templates has helped us to improve the efficiency and quality of our development process, and has made it easier for us to create and maintain high-quality code for our Flutter projects.

Do you need help with setting up a project, or would you like to improve and speed up your development process with code generation?

Share your idea with us, and we’ll come up with the best development solution for your case.

Get in touch today