Kickstarting your Flutter Project with Flutterprint and Mason

Danny Chuang
Flutter Taipei
Published in
6 min readJan 15, 2023
Flutterprint

Creating a new Flutter project from scratch can be a time-consuming and tedious process, especially when you have to set up the same basic features for every project. Imagine having to manually add dependencies, deleting everything and laboriously copying and pasting in all of your standard boilerplate every time you create a new project from the official “counter app”. This is not fun, and this is where mason comes in.

Mason

Mason is an open-source template generator written in Dart. It helps developers to easily generate custom templates called “bricks” 🧱, that represent small or large pieces of code such as classes, functions, or even entire projects. This streamlines the development process by eliminating the need for repetitive boilerplate code, making it easy to create templates with your preferred configurations. If you’re tired of writing repetitive boilerplate code on your daily development, give mason a try.

Flutterprint

To streamline the process of creating a new Flutter project, I created my own Flutterprint brick, which acts as a blueprint that comes with my preferred architecture and basic features I often use. Feel free to try it out and build your own app on top of it.

Getting Started 🚀

This app template can be generated using mason_cli with customized variables.

Ensure you have mason_cli installed.

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

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

Installation

# Install locally
mason add flutterprint

# Or install globally
mason add -g flutterprint

The app template then can be generated by one command 🎉, and you will be prompted to enter project name, organization name, application id, and description.

# Generate the flutterprint app template
mason make flutterprint

What’s included

  • Cross Platform Support — Build in support for iOS, Android and Web.
  • Go Router — A declarative routing package that uses url-based API for navigating between different screens.
  • Internationalization Support — Internationalization support using synthetic code generation to streamline the development process.
  • Riverpod — A reactive caching and data-binding framework.
  • Testing — Unit and Widget Tests
  • Continuous Integration — Lint, format, test, and enforce code coverage using GitHub Actions

Build-in Features

Many apps have the need to communicate with a backend service, whether it’s for storing and retrieving data, or handling authentication. I have included some basic features that I believe will be helpful.

  1. ✨Token-based Authentication Flow
    The app provides a demonstration of a token-based authentication flow, including sign in and refresh token strategy. It uses a fake authentication repository by default, but can easily be configured to use a backend for user authentication by modifying the configuration settings and using the REST API service that is provided by this template. The authentication flow allows users to securely access protected resources and maintain an authenticated state throughout the session.
  2. ✨Backend Environment Switching
    The app demonstrates a feature that allows the user to easily switch between different backend environments (such as release, demo, development, or even localhost) and configure the baseURL and API endpoints for each environment, which enables seamless testing and development.
  3. ✨Redirection Logic
    The app uses go_router combined with riverpod to handle redirect logic that automatically redirects the user to the appropriate pages based on the user’s authenticated state. This feature is handled throughout the app and provides a seamless experience for the user by ensuring that they are always directed to the correct page. For instance, whether the user is authenticated or unauthenticated, they will be redirected to the home page or sign-in page, respectively.
  4. ✨REST API Service
    A service provides a simple interface for performing CRUD operations on a remote server, returning results in the form of Future<Either<ApiFailure, T>> indicating whether the request was successful or not. If the request is successful, T is the generic type of desired domain model deserialized from the json response, otherwise ApiFailure is the union class that provide a pattern-matching way to handle different types of errors.

Project layout

The organization of the directories under ./lib is shown below.

Project layout
  1. constant directory contains constants that are used throughout the app, such as colors, sizes.
  2. features directory contains all the different features of the app, organized into subfolders by feature name. Each feature folder is further divided into subfolders for data, domain, application and presentation layers, which will be explained in more detailed later.
  3. l10n directory contains the localization files that handle translations in the project.
  4. routing directory contains routing-related files, including the routes and a router for handing app’s navigation flow.
  5. services directory contains services that are commonly used to the rest of the app, such as the service that handle communication with remote or local data sources.
  6. utils directory contains utility classes that provide common functionality, such as date formatting and validation functions.
  7. widgets directory contains the widgets that is commonly used throughout the app.

Feature-first structure

The features directory is structured in a feature-first(layers inside features) way to make it easy to add new features and maintain the codebase. This folder contains all the different features of the app, organized into subfolders by feature name. Each feature folder contains subfolders for data, domain, application and presentation layers.

├── lib
│ ├── features
│ │ ├── feature1
│ │ │ ├── presentation
│ │ │ ├── application
│ │ │ ├── domain
│ │ │ └── data
│ │ ├── feature2
│ │ │ ├── presentation
│ │ │ ├── application
│ │ │ ├── domain
│ │ │ └── data

Each layer has its own role for a given feature:

  • The domain layer holds the models that are specific to the feature.
  • The data layer holds the repositories that handle communication with remote or local data sources, providing domain data to the application layer.
  • The application layer handles logic and state management, using data from the data layer to fulfill the app’s requirements.
  • The presentation layer contains the UI widgets that users interact with, rendered based on the state managed in the application layer.

Configurations 🔧

  • Authentication Repository
    The AuthRepository provider uses a fake repository by default for demonstration purpose. To use a real implementation, replace the fake repository with your own implementation in the auth_repository.dart located in the lib/features/authentication/data directory.
  • Backend baseURL and API endpoints
    You can configure the backend base environment in the backend_env.dart and API endpoints in the endpoints.dart that are located in the lib/features/backend_environment/domain directory.
  • Routes
    The routes for the app and the redirection logic are defined and handled in the app_router.dart, which is located in the lib/routing directory.

Running Tests 🧪

To run all unit and widget tests use the following command:

$ flutter test — coverage — test-randomize-ordering-seed random

To view the generated coverage report you can use lcov

# Generate Coverage Report
$ genhtml coverage/lcov.info -o coverage/

# Open Coverage Report
$ open coverage/index.html

Working with Translations 🌐

This project relies on flutter_localizations and follows the official internationalization guide for Flutter.

Adding Strings
To add a new localizable string, open the app_en.arb file at lib/l10n/arb/app_en.arb.

{
"@@locale": "en",
"account": "Account",
"@account": {
"description": "Text shown in the title of account text field"
},
}

Then add a new key/value and description(optional)

{
"@@locale": "en",
"account": "Account",
"@account": {
"description": "Text shown in the title of account text field"
},
"helloWorld": "Hello World",
"@helloWorld": {
"description": "Hello World Text"
}
}

Use the new string

import 'package:flutterprint/l10n/l10n.dart';

@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Text(l10n.helloWorld);
}

Adding Supported Locales

Update the CFBundleLocalizations array in the Info.plist at ios/Runner/Info.plist to include the new locale.

...

<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>zh</string>
</array>

...

Adding Translations
For each supported locale, add a new ARB file in lib/l10n/arb.

├── l10n
│ ├── arb
│ │ ├── app_en.arb
│ │ └── app_zh.arb

Add the translated strings to each .arb file:

app_en.arb

{
"@@locale": "en",
"account": "Account",
"@account": {
"description": "Text shown in the title of account text field"
},
}

app_zh.arb

{
"@@locale": "zh",
"account": "帳號",
"@account": {
"description": "用於帳號欄位的標題文字"
},
}

Conclusion

The purpose of creating Flutterprint is to streamline the process of starting a new app by reducing the time spent on writing commonly used code.

Overall, Mason is a simple and easy-to-use templating tool for not only creating a full project template, but also customizing your own “bricks” of frequently used code to improve your daily development efficiency or standardize your team’s development style.

Credits and Acknowledgments

This project was originally developed by Very Good Ventures 🦄, and it has been forked and adapted for creating different App templates based on my personal preferred architecture and features. Some of the project’s structure and test flow were inspired by Andrea Bizzotto. I would like to acknowledge and thank him for the inspiration he provided through his work.

--

--