Skip to main content

Generated Code in Flutter ‘Mason’

Updated 5 min readMarkdown

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:

  1. Domain Layer: Contains the core business logic. It includes:
  • Entities: The core data objects.
  • Use Cases: Interactors that coordinate logic and data flow.
  1. 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.
  1. 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

  1. 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.dart
  • lib/data/repositories/login_repository.dart
  • lib/presentation/cubit/login_cubit.dart
  • lib/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 LoginRepository into your LoginUseCase.
  • Call performLogin() from your LoginCubit to trigger login functionality.
  • Bind LoginCubit to your UI and use states like LoginLoading and LoginLoaded to 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" "

No comments yet. Be the first to comment.

More from this blog