Skip to content
Back to archipelago.banuacoder.com

Core Scaffolding

How to add a new feature module to a Flutter monorepo ​

Free Reference for feature_monorepo_skeleton: every variable, option and package it generates. Command: archipelago generate feature_monorepo_skeleton

Create a feature module in an Archipelago monorepo: pick single package or API/Impl, wire it into the host app, then add routes, DI and logic layers.

What you'll learn ​

  • Creating a new feature module inside your monorepo
  • Choosing between single-package and API/Impl split patterns
  • Wiring the feature into the host app through a manifest and a lazy SDK
  • Adding routes, DI, and business logic layers

Prerequisites ​

Step 1: Generate the Feature ​

From your monorepo root, run:

bash
archipelago generate feature_monorepo_skeleton

Answer the prompts:

  • featureName: Payment (PascalCase, becomes payment in paths)
  • isShared: false for a self-contained feature, true if other features depend on it
  • hasBusinessLogic: true to include repos, usecases, and DI layers
  • hasLocale: true if the feature manages its own translations
  • isPlugin: true if the feature needs native Android and iOS code. It adds android/, ios/ and a Dart MethodChannel wrapper (see the brick reference)

Step 2: Non-Interactive Config ​

json
{
  "featureName": "Payment",
  "isShared": false,
  "hasBusinessLogic": true,
  "hasLocale": true,
  "isPlugin": false,
  "includeGenerated": false
}
bash
archipelago generate feature_monorepo_skeleton --config payment_config.json

Step 3: Single-Package Structure ​

When isShared: false, you get a self-contained feature:

text
features/
└── payment/
    ├── lib/
    │   ├── payment.dart                 # Barrel export
    │   └── src/
    │       ├── payment_manifest.dart    # Routes, read at boot
    │       ├── payment_sdk.dart         # Port + lazy implementation
    │       ├── data/
    │       │   ├── datasources/
    │       │   │   ├── local/
    │       │   │   └── remote/
    │       │   ├── models/
    │       │   └── repositories/
    │       ├── di/
    │       │   ├── injector.dart                # Generated local DI
    │       │   ├── payment_container.dart       # paymentContainer (child GetIt)
    │       │   ├── payment_global_module.dart   # Manifest + lazy SDK
    │       │   └── payment_imports.dart         # Root types the feature uses
    │       ├── domain/
    │       │   ├── entities/
    │       │   ├── repositories/
    │       │   └── usecases/
    │       ├── presentation/
    │       │   ├── payment_shell_page.dart
    │       │   └── ui/pages/
    │       └── router/
    │           └── payment_router.dart
    ├── test/di/payment_di_test.dart     # Lazy SDK + container isolation
    └── l10n/                            # Localization files

Step 4: API/Impl Split Pattern ​

Set isShared: true when the feature exposes contracts to other features (e.g., auth status checks). This generates two packages:

text
features/
├── payment_api/          # Port (plain interface) and shared models
│   └── lib/src/
│       └── payment_sdk.dart
└── payment_impl/         # Full implementation
    └── lib/src/
        ├── payment_manifest.dart
        ├── payment_sdk_impl.dart
        ├── data/
        ├── di/
        ├── domain/
        ├── presentation/
        └── router/

Other features depend on payment_api only, never on payment_impl. The host app wires the impl at the DI level.

Step 5: Check the Registration ​

You do not register the feature by hand. The brick's post_gen hook:

  • adds the package(s) to the workspace,
  • adds the feature (payment_impl for the shared variant) to the app's pubspec.yaml,
  • registers PaymentGlobalModule (PaymentImplGlobalModule for the shared variant) in the app's lib/di/injector.dart, and declares it under archipelago: di_modules: in the feature pubspec.

The global module registers PaymentManifest with FeatureSDKRegistry, so its routes exist before the router is built. It registers PaymentSDK as a lazy singleton. PaymentSDKImpl is built only when something resolves it. Run dart pub get and melos run build to generate DI and routes. Then run flutter test test/di in the feature to check the lazy SDK and the container isolation.

Step 6: Add Dependencies ​

Repositories, datasources and use cases live in the feature's own container, not the app root:

  1. Annotate them with @injectable or @lazySingleton. Then build_runner adds them to initPaymentLocalDI.

  2. For each app-root type they take (a network client, storage), add one line to payment_imports.dart:

    dart
    child.registerFactory<HttpClient>(() => root<HttpClient>());
  3. In PaymentShellPage, pass what the state holder needs from the lease:

    dart
    create: (_) => PaymentCubit(_lease.get<LoadPaymentsUseCase>())..load(),

The container never falls back to the root, so a type you forgot to import fails loudly instead of resolving another feature's registration.

State Management Variable ​

The feature_monorepo_skeleton brick accepts a stateManagement variable that generates a state management skeleton alongside the feature module structure.

Options: bloc / cubit / riverpod / provider / none

ValueGenerated files
cubit_cubit.dart, _state.dart, _cubit_test.dart
bloc_bloc.dart, _event.dart, _state.dart, _bloc_test.dart
riverpod_notifier.dart, _state.dart, _notifier_test.dart
provider_notifier.dart, _notifier_test.dart
noneNo state layer is generated. The existing ValueNotifier presentation shell stays

Example: generate a feature with a cubit:

bash
archipelago generate feature_monorepo_skeleton --featureName Payment --stateManagement cubit

Choosing none leaves the default ValueNotifier in the presentation shell untouched. This is the safest choice when you want to add state management later or prefer to manage it yourself.

For more detail on each variant, see the state_management brick tutorial.

When to Use Each Pattern ​

PatternUse When
Single packageFeature is self-contained, no other module needs its contracts
API/Impl splitOther features depend on this feature's types or status
No business logicPure UI feature (e.g., onboarding slides, static about page)
With localeFeature has user-facing strings that need translation

Next Steps ​