🧱 moarch

A Flutter CLI tool to scaffold Clean Architecture projects with Riverpod β€” your conventions, your structure, no code generation required.

pub version license: MIT


Features

  • moarch init β€” scaffolds your full lib/ structure in seconds
  • moarch create feature <n> β€” generates Clean Architecture layers with an interactive checklist
  • No build_runner, no freezed, no riverpod_annotation β€” everything compiles immediately
  • Providers live at the top of their own file, no separate DI file
  • State pattern matches your own style: plain class with safe copyWith, AsyncNotifier, ref.listen
  • .env and .fvmrc generated at project root

Installation

dart pub global activate moarch

Make sure ~/.pub-cache/bin is in your PATH:

# add to .zshrc or .bashrc
export PATH="$PATH:$HOME/.pub-cache/bin"

Quick start

# 1. create your Flutter project
flutter create my_app && cd my_app

# 2. add dependencies to pubspec.yaml (see below)
flutter pub get

# 3. remove the generated main.dart
rm lib/main.dart

# 4. scaffold
moarch init

# 5. create your first feature
moarch create feature auth

Required project dependencies

dependencies:
    flutter:
        sdk: flutter
    flutter_riverpod: ^2.5.1
    dio: ^5.4.3
    flutter_dotenv: ^5.1.0
    flutter_secure_storage: ^9.2.2

Also register .env in your pubspec.yaml assets:

flutter:
    assets:
        - .env

moarch init

Generates the full project structure:

.env                             ← BASE_URL=
.fvmrc                           ← { "flutter": "stable" }
lib/
β”œβ”€β”€ main.dart
β”œβ”€β”€ core/
β”‚   β”œβ”€β”€ constants/
β”‚   β”‚   β”œβ”€β”€ app_constants.dart   ← spacing (4pt grid), text sizes, touch targets, radii, durations
β”‚   β”‚   └── api_constants.dart   ← timeouts only, BASE_URL comes from .env
β”‚   β”œβ”€β”€ errors/
β”‚   β”‚   └── app_exception.dart
β”‚   β”œβ”€β”€ network/
β”‚   β”‚   └── dio_client.dart      ← dotenv baseUrl, secure storage auth token, all status codes pass through
β”‚   β”œβ”€β”€ usecases/
β”‚   β”‚   └── usecase.dart
β”‚   └── utils/
β”‚       β”œβ”€β”€ extensions.dart      ← ContextX, StringX, DateTimeX
β”‚       └── logger.dart          ← single log() function, kDebugMode only
β”œβ”€β”€ config/
β”‚   └── theme/
β”‚       └── app_theme.dart       ← useMaterial3, you fill in the rest
β”œβ”€β”€ shared/widgets/
β”‚   β”œβ”€β”€ app_button.dart          ← filled / outlined / text variants
β”‚   β”œβ”€β”€ app_loading.dart
β”‚   └── error_view.dart
└── features/

moarch create feature <n>

Generates Clean Architecture layers with an interactive checklist in the terminal.

moarch create feature auth
moarch create feature user_profile
moarch create feature ProductCatalog    # any casing works

moarch create feature auth --all        # skip checklist, generate all layers

Checklist β€” toggle with space, confirm with enter:

  Select layers for "Auth":
β–Ά [βœ“]  Remote Datasource
  [ ]  Local/Cache Datasource        ← off by default
  [βœ“]  Repository (interface + impl)
  [ ]  Use Cases                     ← off by default
  [βœ“]  State + Notifier
  [βœ“]  View

Generated structure:

lib/features/auth/
β”œβ”€β”€ domain/
β”‚   β”œβ”€β”€ entities/auth_entity.dart
β”‚   β”œβ”€β”€ repositories/auth_repository.dart
β”‚   └── usecases/get_auth.dart          ← if selected
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ datasources/
β”‚   β”‚   β”œβ”€β”€ auth_remote_datasource.dart
β”‚   β”‚   └── auth_local_datasource.dart  ← if selected
β”‚   β”œβ”€β”€ models/
β”‚   β”‚   └── auth_model.dart
β”‚   └── repositories/
β”‚       └── auth_repository_impl.dart
└── presentation/
    β”œβ”€β”€ states/auth_state.dart
    β”œβ”€β”€ notifiers/auth_notifier.dart
    β”œβ”€β”€ views/auth_view.dart
    └── widgets/

State pattern used:

class AuthState {
  const AuthState({
    this.isLoadingAction = false,
    this.error,
    this.success,
  });

  final bool isLoadingAction;
  final String? error;
  final String? success;

  AuthState copyWith({
    bool? isLoadingAction,
    String? error,
    String? success,
  }) {
    return AuthState(
      isLoadingAction: isLoadingAction ?? false,
      error: error,
      success: success,
    );
  }
}

Error handling in the view uses state.value?.error β€” your AppException message from the repository β€” not the AsyncValue error:

ref.listen(authNotifierProvider, (_, next) {
  final value = next.value;
  if (value?.error != null) {
    // show snackbar with value.error
  }
});

Customizing moarch

All customization is in lib/src/templates/ β€” edit the string inside any method to change what gets generated:

File Controls
core_templates.dart main.dart, dio_client, constants, errors, utils
config_templates.dart theme
shared_templates.dart app_button, app_loading, error_view
feature_templates.dart entity, model, datasources, repository, state, notifier, view

After any change, re-activate:

dart pub global activate --source path /path/to/moarch

License

MIT Β© AndrΓ© Montoito

Libraries

moarch