Skip to content
Back to archipelago.banuacoder.com

Getting Started

Generating Features ​

What you'll learn ​

  • How the ephemeral config flow works
  • Adding a new feature module to your monorepo
  • Understanding what gets generated and why
  • Checking how the feature registers with the host app

Prerequisites ​

Here is the whole flow for a feature called orders, from the command to the app wiring:

archipelago generateOne command adds a feature and wires it into the app

Step 1 of 6, config: The CLI loads the brick, then writes a config file with the brick defaults into the current directory and waits. You edit the values, here the name orders, and press Enter. The real file also holds an @ description line above each key, left out here.

archipelago generate feature_monorepo_skeleton✓ Loaded brick: feature_monorepo_skeleton  ┌──────────────────────────────────────────────────────────┐  │  Config file created:                                    │  │  /home/dev/my_app/feature_monorepo_skeleton_config.json  │  │                                                          │  │  Edit the values and press Enter to                      │  │  continue generation...                                  │  └──────────────────────────────────────────────────────────┘Config file created:  /home/dev/my_app/feature_monorepo_skeleton_config.jsonEdit the values, then press Enter to continue.Keys starting with "@" are descriptions — they are stripped automatically.Press Enter when done editing...
apps/template_app/lib/di/injector.dart
import 'package:network_sdk_dio/network_sdk_dio.dart';import 'package:package_info_provider/package_info_provider.dart';// …    ExternalModule(NetworkSdkDioPackageModule),    ExternalModule(PackageInfoProviderPackageModule),    // archipelago:feature_modules_before:end
feature_monorepo_skeleton_config.json
{  "featureName": "orders",  "isShared": false,  "hasBusinessLogic": true,  "hasLocale": false,  "includeGenerated": false,  "stateManagement": "cubit",  "prefix": "App"}

How Ephemeral Config Works ​

When you generate a feature, Archipelago uses an ephemeral configuration flow:

  1. The CLI creates a temporary config template in the current directory
  2. You fill in the values (feature name, options) and save
  3. The brick is invoked with that config
  4. Generated files are written to your project
  5. The temporary config file is deleted

In CI, pass --config <file.json> instead and the interactive step is skipped. Nothing is stored in your project, so you never end up with stale config files.

Step 1: Generate a Feature ​

Run the generator from the project root. generate takes the exact brick slug:

bash
archipelago generate feature_monorepo_skeleton

The config asks for:

  • featureName: Used for package and class naming
  • isShared: true for an _api / _impl split (when other features consume it), false for a single package
  • hasBusinessLogic: Include repositories, use cases and local DI
  • hasLocale: Give the feature its own slang translations
  • isPlugin: Add native Android and iOS code (a Flutter plugin) with one MethodChannel
  • stateManagement: bloc, cubit (default), riverpod, provider or none
  • includeGenerated: Whether generated files are committed

Step 2: Review the Output ​

The feature always lands flat under features/. A single package (isShared: false) looks like:

text
features/payments/
├── lib/
│   ├── payments.dart            # Barrel export
│   └── src/
│       ├── payments_manifest.dart  # Routes, read at boot
│       ├── payments_sdk.dart       # Port and lazy implementation
│       ├── data/                # if hasBusinessLogic
│       ├── di/                  # if hasBusinessLogic
│       ├── domain/              # if hasBusinessLogic
│       ├── presentation/
│       └── router/
├── l10n/                        # if hasLocale
├── slang.yaml                   # if hasLocale
├── android/, ios/               # if isPlugin
├── build.yaml
└── pubspec.yaml

With isShared: true you get two packages instead, features/payments_api/ (contract only) and features/payments_impl/ (implementation, DI, presentation, router).

Step 3: Register the Feature ​

Features register themselves. The brick adds the new packages to the workspace and the feature to the app pubspec.yaml. It registers the global module in the app injector.dart. You only resolve and generate:

bash
dart pub get
melos run build                                                # generate DI and routes

The feature's global module registers its FeatureManifest with FeatureSDKRegistry. The manifest adds its routes before the router is built. The SDK is a lazy singleton, built on first use. See Feature Manifests and SDKs.

Step 4: Wire Up Dependencies ​

If your feature depends on other SDKs, add them as workspace dependencies and let injectable resolve them:

bash
dart run monorepo_toolkit deps internal payments_impl network_sdk_api
dart
// features/payments_impl/lib/src/data/payments_repository_impl.dart
@LazySingleton(as: PaymentsRepository)
class PaymentsRepositoryImpl implements PaymentsRepository {
  PaymentsRepositoryImpl(this._network);

  final HttpClient _network;
}

With hasBusinessLogic: true these registrations go into the feature's own container (<feature>_container.dart). HttpClient comes from the app root, so forward it in <feature>_imports.dart: child.registerFactory<HttpClient>(() => root<HttpClient>());. See Dual-GetIt. Run flutter test test/di in the feature to check the wiring.

When to Use API/Impl Split ​

ScenarioUse API Split?
Feature consumed by other featuresYes
You plan to swap the vendor implementationYes
Self-contained featureNo
Debug/release behavior differenceUse impl/noop instead

Next Steps ​

Dive deeper into the architecture patterns in Understanding API/Impl Split.