Scaffolding
app_scaffold Free
- Tier
- Freeversion 2.1.4
- Command
archipelago generate app_scaffold- Generates
apps/<app_name>
- Depends on
- shared_app_config, shared_consent, app_utilities, monitoring_sdk, launch_tracker_sdk, flutter_l10ngenerate these first
Generate a new app in an existing Archipelago v2 workspace. Creates the full app shell with DI, router, bootstrap, flavors, Fastlane, launcher icons, and splash screen configuration.
Variables
| Variable | Type | Default | Description |
|---|---|---|---|
| appName | string | MyApp | The name of the application |
| organization | string | MyCompany | Your organization or company name |
| domain | string | com | Your organization top-level domain. See App and Bundle IDs for how they become the Android and iOS IDs. |
| prefix | string | App | Unique prefix for component naming |
Install
Interactive
archipelago generate app_scaffoldNon-interactive (CI)
archipelago generate app_scaffold --config my_config.jsonRun this inside an existing Archipelago workspace (one already scaffolded by flutter_modular_monorepo) to add a second (or later) app — the UI kit, shared packages, and infrastructure already need to exist. post_gen scans packages/ for the sole *_ui_kit directory and rewrites the rendered app_ui_kit import/dependency in pubspec.yaml and every Dart file under lib/ to match it, aborting loudly if it finds zero or more than one candidate.
post_gen also adds apps/<app_name> to the workspace, to flavors.yaml, to the IDE run configurations (.vscode/launch.json, .vscode/tasks.json, .run/, merged with the other apps; see IDE run configurations), and to target_paths in build_prepare.yaml. Without the last one, build-prepare --flavor production and --check-leaks skip the new app (1.6.0 and later). The new app has no debug tooling of its own; add app_debugger or app_blackbox to it by hand if you need them. It does depend on the workspace's features/app_marionette (Marionette MCP in debug builds only, swapped out by build-prepare --mode release|profile), which flutter_modular_monorepo 5.0.0 and later generates (there is no brick of its own). In an older workspace pre_gen stops with "Nothing was changed": copy features/app_marionette/ from a fresh project (archipelago create --output <scratch dir>) and run the brick again. post_gen then adds the Marionette debug_build_only entries to build_prepare.yaml when they are missing.
After generation, run dart pub get, then dart run monorepo_toolkit flavor generate --app <app_name> from the workspace root, then dart run build_runner build in apps/<app_name>. The toolkit runs every flavorizr instruction in the app's flavorizr.yaml without prompting. The new app's RunnerUITests target (Patrol on iOS) is added before the flavors exist, so it has only the Debug, Release and Profile configurations; flavored schemes still list it.
Config Template
{
"@appName": "The name of the application",
"appName": "MyApp",
"@organization": "Your organization or company name",
"organization": "MyCompany",
"@domain": "Your organization top-level domain",
"domain": "com",
"@prefix": "Unique prefix for component naming",
"prefix": "App"
}Generated Structure
apps/
└── {app_name}/
├── .well-known/
│ ├── apple-app-site-association
│ └── assetlinks.json
├── android/app/ # build.gradle.kts, proguard-rules.pro, MainActivity.kt
├── ios/Runner/AppDelegate.swift
├── fastlane/
│ ├── Appfile
│ ├── Fastfile
│ ├── Matchfile
│ └── Pluginfile
├── launcher/ # App icons per flavor
│ ├── ios-logo.png
│ ├── ios-logo-dev.png
│ ├── ios-logo-stg.png
│ ├── logo.png
│ ├── logo-dev.png
│ ├── logo-stg.png
│ ├── logo-background.png
│ ├── logo-background-dev.png
│ ├── logo-background-stg.png
│ └── logo-foreground.png
├── splash/ # Splash screen assets
├── lib/
│ ├── app.dart
│ ├── bootstrap.dart
│ ├── main.dart
│ ├── main_development.dart
│ ├── main_staging.dart
│ ├── di/injector.dart
│ ├── host/
│ │ ├── host_app_widget.dart
│ │ ├── services/deeplink_service.dart
│ │ └── initializers/
│ │ ├── initializer.dart
│ │ ├── initializer_runner.dart
│ │ ├── pre_launch_initializer.dart
│ │ └── post_launch_initializer.dart
│ └── router/app_router.dart
├── integration_test/ # Patrol/integration_test scaffolding
├── test/
│ ├── architecture/feature_boundary_test.dart # No feature depends on another's _impl
│ ├── di/injector_test.dart # Composition-root smoke test
│ ├── di/{lazy_feature_sdk,base_scope_budget,port_singleton}_test.dart # DI guard tests
│ └── support/composition_root_harness.dart, feature_files.dart # Harness filled by leaf bricks
├── Gemfile
├── flavorizr.yaml
├── flutter_launcher_icons-development.yaml
├── flutter_launcher_icons-production.yaml
├── flutter_launcher_icons-staging.yaml
├── flutter_native_splash.yaml
└── pubspec.yamlPublic API
app.dart is the MaterialApp.router root: it takes an AppRouter and applies {prefix}Theme.light()/.dark(). bootstrap.dart runs the layered error-handling setup (Flutter framework errors, platform-dispatcher errors, runZonedGuarded), installs consent (ConsentInitializer.initialize(GetIt.I), so the ConsentGate is resolvable and telemetry is not stuck fail-closed), leaves an archipelago:firebase:init marker region for Firebase-based bricks, restores the saved locale, calls initializeInjector(), then runApps a HostAppWidget:
Future<void> bootstrap({
required FlavorStatus flavor,
required AppBuilder builder,
}) => runZonedGuarded(() async {
// ... error handlers, ConsentInitializer.initialize(), AppLocaleController.restore(), initializeInjector() ...
runApp(HostAppWidget(
initializerController: PreLaunchInitializerController.create(),
builder: (context, result) => builder(result),
loadingWidget: const _BootstrapLoadingIndicator(),
));
}, (error, stackTrace) { /* uncaught async errors */ });HostAppWidget drives the two-phase initialization lifecycle: pre-launch runs in initState and blocks the UI (showing loadingWidget; loading and error states render inside a themed MaterialApp shell and the error state is retryable) until PreLaunchInitializerController.create().initialize() resolves; initializer_runner.dart applies per-initializer timeouts and criticality and reports failures to AppMonitoring; the pre-launch sweep seals FeatureSDKRegistry and only initializes enabled SDKs. The first post-frame callback then runs PostLaunchInitializer.create(context:, router:) non-blockingly. For the launch trace's native milestones, bootstrap() calls markDartMain() first and AppDelegate calls LaunchTimeTrackerPlugin.markAppInitStart() inside #if canImport(app_launch_tracker_impl); Android needs no host code (see launch_tracker_sdk). AppRouter extends RootStackRouter composes routes purely from RouteRegistry._registry.routes — feature SDKs register their own routes during pre-launch, so this file never lists routes directly.
DI & Wiring
di/injector.dart is the @InjectableInit composition root, registering AppUtilitiesPackageModule and RouterRegistryPackageModule as external modules before the archipelago:feature_modules_before:/archipelago:feature_modules_after: marker pairs that feature bricks inject into. archipelago:feature_imports: is the matching import marker pair.
test/di/injector_test.dart runs initializeInjector() under flutter test. Its only setup is installCompositionRootPrerequisites() from test/support/composition_root_harness.dart. A new app starts with an empty harness. A brick that you install into the app later adds its own test prerequisite to the harness. See Testing.
The app also ships the DI guard tests of flutter_modular_monorepo, identical apart from the app name: feature boundary, lazy SDKs, boot budget and port identity. See DI Guard Tests.
Dependencies
Generated app pubspec.yaml: get_it ^9.2.1, flutter_native_splash ^2.4.8, plus workspace path dependencies including consent_impl, locale_core and app_launch_tracker_impl; dev: flutter_launcher_icons ^0.14.4, flutter_flavorizr ^2.5.0, change_app_package_name ^1.5.0, patrol ^4.8.0, integration_test (SDK), auto_route_generator ^10.5.0, injectable_generator ^3.0.2, logger ^2.7.0 (used by injector_test.dart).
Platform Setup
post_gen runs flutter create in a scratch directory and merges the result into android//ios/ (backfilling Podfile, Runner.xcodeproj, the Gradle wrapper, AndroidManifest.xml — files this brick doesn't template itself) via shared_hooks' PlatformScaffoldHelper. The merge only adds files missing from the target; it never overwrites this brick's own customized files (android/app/build.gradle.kts, ios/Runner/AppDelegate.swift, android/app/proguard-rules.pro). If flutter create fails (e.g. flutter not on PATH), generation still succeeds and prints the exact manual flutter create apps/<name> --org <org> command to run instead. It also generates a debug keystore and computes the SHA-256 fingerprint for .well-known/assetlinks.json (advisory — a failing keytool just leaves the placeholder untouched).
Android Release Build
android/app/build.gradle.kts's release build type ships with isMinifyEnabled/isShrinkResources on and proguardFiles(...) wired to android/app/proguard-rules.pro. That file carries baseline @Keep-respecting and stack-trace-preserving rules, plus an archipelago:proguard:keep-rules marker pair — capability bricks (webview_sdk, webview_sdk_advanced, biometric_auth_sdk) append their own conditional keep rules there at generation time, so a release build only carries the rules the app's actual dependency tree needs.
An archiveDebugSymbols Gradle task is also registered:
./gradlew :app:archiveDebugSymbols -Pvariant=releaseRun after a release build — copies that variant's R8 mapping.txt and native-debug-symbols bundle into build/outputs/debug-artifacts/<variant>/, a stable path CI or a developer can upload to a crash-reporting vendor for deobfuscation.
Limitations
MaterialApp.routerhas an emptyarchipelago:app_buildermarker region for itsbuilder:parameter;app_debuggerfills it with the debug overlay (1.6.0 and later).router.config(...)passesnavigatorObservers: () => [...]with an emptyarchipelago:navigator_observersregion;journey_trackeradds its observer there (2.0.0 and later).AppRouter.initialPathships as a hardcoded'/'with aTODO— auth/introduction gating logic is left to the developer.
Changelog
Run archipelago brick info app_scaffold to see the latest published version. Enterprise customers can read the full CHANGELOG.md in the brick source repository.