Generated Code in Flutter ‘Mason’

Peace upon you,
Here’s an article explaining how the Mason package can be beneficial in Flutter development, especially with a clean architecture setup and cubit state management.
Here’s an article explaining how the Mason package can be beneficial in Flutter development, especially with a clean architecture setup and cubit state management.
What is Mason?
Mason is a powerful code-generation package for Dart and Flutter. It works by using “bricks,” which are customizable templates you can configure to generate entire code structures, files, and even complex modules.
In essence, Mason helps you automate repetitive code, ensure consistent architecture, and save development time by generating the common elements in your codebase. Whether you’re building new screens, setting up state management, or adding data models, Mason can streamline the process, especially in projects that follow Clean Architecture and utilize state management tools like Cubit.
Why Use Mason?
In Clean Architecture, you’ll often find yourself repeatedly creating similar code structures in each layer, especially for features like login, signup, or fetching data. Writing and setting up this boilerplate code manually can be tedious and error-prone. Here are a few ways Mason can help:
- Consistency: Enforces a consistent structure, regardless of the developer working on a feature.
- Reduced Boilerplate: Speeds up development by auto-generating foundational files.
- Scalability: Allows you to replicate best practices easily, making projects more scalable and easier to maintain.
- Time Efficiency: Significantly reduces the time spent on setting up new features, freeing you to focus on custom logic.
Mason in Clean Architecture with Cubit
In Clean Architecture, a typical Flutter app has three layers:
- Domain Layer: Contains the core business logic. It includes:
- Entities: The core data objects.
- Use Cases: Interactors that coordinate logic and data flow.
- Data Layer: Manages data sources, including APIs, local storage, and repositories.
- Models: Represent the data structure received from or sent to external sources.
- Repositories: Abstract data access, hiding details of APIs or databases.
- Data Sources: Classes that handle direct API or database operations.
- Presentation Layer: Manages the UI and user interaction.
- Cubit: Manages state with freezed and orchestrates logic, handling UI events and communicating with the domain layer.
- Screens/Widgets: Display data to the user and trigger events.
Step-by-Step Example: Using Mason for a Login Feature
Let’s look at a concrete example of setting up a Login feature with Mason, which spans across all three layers:
Step 1: Set Up Mason in Your Project
- Install Mason CLI:
dart pub global activate mason_clib
2. Initialize Mason: In your Flutter project root, create a mason.yaml file to configure your bricks:
bricks:
usecase:
path: bricks/usecase
repository:
path: bricks/repository
cubit:
path: bricks/cubit
Install Bricks: After defining your bricks, run:
mason get
Step 2: Create Bricks for Each Layer
For each layer, create a template brick. For this example, let’s create bricks for the Use Case (Domain Layer), Repository (Data Layer), and Cubit (Presentation Layer).
1. Domain Layer: Use Case Brick
In bricks/usecase, create a file {{name}}_usecase.dart:
// domain/usecases/{{name}}_usecase.dart
class {{name.pascalCase}}UseCase {
Future<void> execute() async {
// Add your use case logic here
}
}
Here’s what this does:
{{name}}is a variable that will be replaced with the specific name of the use case (e.g.,Login).{{name.pascalCase}}will automatically capitalize the variable (e.g.,LoginUseCase).
2. Data Layer: Repository Brick
In bricks/repository, create a file {{name}}_repository.dart:
// data/repositories/{{name}}_repository.dart
import '../../domain/entities/{{name}}_entity.dart';
class {{name.pascalCase}}Repository {
Future<{{name.pascalCase}}Entity> fetch{{name.pascalCase}}Data() async {
// Implement data fetching logic
return {{name.pascalCase}}Entity();
}
}
This template sets up a skeleton for a repository that communicates with the data sources and returns an entity for the domain layer.
3. Presentation Layer: Cubit Brick
In bricks/cubit, create two files: one for the Cubit ({{name}}_cubit.dart) and one for its states ({{name}}_state.dart).
- Cubit (
{{name}}_cubit.dart):
// presentation/cubit/{{name}}_cubit.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import '{{name}}_state.dart';
class {{name.pascalCase}}Cubit extends Cubit<{{name.pascalCase}}State> {
{{name.pascalCase}}Cubit() : super(const {{name.pascalCase}}State.initial());
Future<void> perform{{name.pascalCase}}() async {
emit(const {{name.pascalCase}}State.loading());
try {
// Perform async operation
final data = await fetch{{name.pascalCase}}Data();
emit({{name.pascalCase}}State.loaded(data));
} catch (e) {
emit({{name.pascalCase}}State.error(e.toString()));
}
}
Future<{{name.pascalCase}}Data> fetch{{name.pascalCase}}Data() async {
// Simulate data fetching
await Future.delayed(Duration(seconds: 2));
return {{name.pascalCase}}Data();
}
}
State ({{name}}_state.dart):
// presentation/cubit/login_state.dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'login_state.freezed.dart';
@freezed
class LoginState with _$LoginState {
const factory LoginState.initial() = LoginInitial;
const factory LoginState.loading() = LoginLoading;
const factory LoginState.loaded(LoginData data) = LoginLoaded;
const factory LoginState.error(String message) = LoginError;
}
This structure sets up a Cubit with an initial state, a loading state, a success state, and an error state.
Step 3: Generate Code Using Mason
With the bricks configured, you can now generate your code. For a Login feature, run:
mason make usecase -o lib/domain/usecases --name login
mason make repository -o lib/data/repositories --name login
mason make cubit -o lib/presentation/cubit --name login
This command will generate the following files:
lib/domain/usecases/login_usecase.dartlib/data/repositories/login_repository.dartlib/presentation/cubit/login_cubit.dartlib/presentation/cubit/login_state.dart
Each file will have the predefined structure you set in your bricks, with login automatically replaced throughout the code.
Step 4: Use the Generated Code
The generated files are ready to be used in your project. For example:
- Inject
LoginRepositoryinto yourLoginUseCase. - Call
performLogin()from yourLoginCubitto trigger login functionality. - Bind
LoginCubitto your UI and use states likeLoginLoadingandLoginLoadedto update the user interface.
Conclusion
With Mason, you can efficiently manage the setup of Clean Architecture and Cubit in your Flutter projects. Using bricks, you can standardize each layer, making it easier to maintain and scale your codebase. By automating repetitive tasks, Mason frees up more time for custom logic and feature development, creating a powerful productivity boost for Flutter developers.
References
https://www.youtube.com/watch?v=qjA0JFiPMnQ
Originally published on Medium on October 12, 2024.
About the author
- Software Engineer FullStack Developer " nest-js & nuxt js (vue.js) & Flutter"Mobile" "
Comments
Join the discussionNo comments yet. Be the first to comment.