Shared Packages
How to enforce architecture rules in a Flutter monorepo with lints
Free Reference for app_lints: every variable, option and package it generates. Command: archipelago generate app_lints
Generate the app_lints analyzer plugin, learn its seven rules, fix the lint messages, and adopt the DI rules in an older project.
What you'll learn
- Generating the
app_lintsanalyzer plugin - Understanding the seven rules it enables
- Reading and fixing the lint messages
- Adopting the DI rules in an older project
Prerequisites
- An existing Archipelago monorepo (see Monorepo Scaffolding)
New projects already include this brick. Run it on an older project.
Step 1: Generate the Lint Package
archipelago generate app_lintsThe prompts are:
- appName: the name of your app
- isForMonorepo: leave this as
true
Or use a config file:
{
"appName": "MyApp",
"isForMonorepo": true
}archipelago generate app_lints --config lints_config.jsonStep 2: Understand the Generated Structure
The brick creates an analyzer plugin. It is not a lint rule package that you edit often.
utilities/
└── app_lints/
├── lib/
│ ├── main.dart # AppLintsPlugin registers the 7 rules
│ └── src/
│ ├── pii/ # Personal-data key matcher and call scanner
│ └── rules/ # One file per rule
├── test/ # Rule tests over an in-memory workspace
├── analysis_options.yaml
└── pubspec.yamlStep 3: Know the Seven Rules
The rules run during dart analyze and in your IDE.
Layer rules
api_no_external_deps: an*_apipackage imports onlydart:*, shared packages, other_apipackages and a short list of pub packages.no_impl_cross_import: an*_implpackage never imports another*_implpackage. Go through the_apipackage.no_upward_dependency: the layer order isapps,features,infrastructure,packages,shared. A lower layer never imports a higher one.
The layer comes from the folder of the resolved file, not from the package name.
DI rules (feature code only)
no_service_locator_outside_di: aGetItvalue appears only inlib/src/di/or in aMicroPackageModule. Take dependencies as constructor parameters.feature_global_module_allowlist: a feature global module registers only itsFeatureManifest, its own SDK, or a line marked// di-global: <reason>.no_service_locator_in_manifest: a manifest never readsGetItin its constructor,isEnabledorregisterRoutes. Pass a provider closure instead.
Privacy rule
no_pii_in_telemetry: flags personal-data keys in maps passed totrack,log,breadcrumb,recordErrororcaptureCustomEvent.
Step 4: Check the Root analysis_options.yaml
The generated root file enables all three DI rules:
plugins:
app_lints:
path: utilities/app_lints
diagnostics:
feature_global_module_allowlist: true
no_service_locator_outside_di: true
no_service_locator_in_manifest: trueRun the analyzer to see findings:
dart analyzeStep 5: Fix or Suppress Findings
Fix a service-locator finding by moving the lookup:
- Pages get their dependencies from the shell page lease or a route builder.
- State holders take dependencies by constructor.
Use // di-global: <reason> only for a port that code outside the feature resolves from the root.
An older project can adopt the rules step by step. Set no_service_locator_outside_di: false until you migrate. Or suppress single sites:
// ignore: app_lints/no_service_locator_outside_diThe app_lints/ prefix is required for plugin diagnostics.
Key Customization Points
| Customization | Where to Change |
|---|---|
| Turn a rule off | The diagnostics map in the root analysis_options.yaml |
| Suppress one site | An // ignore: app_lints/<rule> comment |
| Change the personal-data key list | lib/src/pii/pii_key_matcher.dart |
| Add a rule | A new file in lib/src/rules/, registered in lib/main.dart |
Known Limitations
- The DI rules report in the IDE and
dart analyzeonly. No CI command gates them. - Generated
*.module.dartfiles are not checked. - There is no baseline file yet.
Next Steps
- Set up coverage management to enforce test coverage alongside lint rules
- Generate the core infrastructure