Every package in the workspace, what it owns, and what it deliberately doesn't.
Grouped by layer, outermost first. Click a row to open it β key files, the contract it publishes, and the one rule worth remembering about it.
/app
The composition root
The only leaf in the graph, and the only package that knows about
concrete _impl packages. Renaming the app, adding a flavor, or
registering a feature all happen here.
appβΆ
Owns the app widget, flavors, and all composition in
bootstrap/. Three files carry
// <generated:...> anchor comments that
modular new feature writes above β never remove them.
Startup, in order: WidgetsFlutterBinding.ensureInitialized()
β DependencyInjectionConfiguration.initialize() β
RouterConfiguration.initialize(container) β runApp
wrapping DependencyScope β RouterScope β
AppThemeScopeWrapper β AppLocaleScopeWrapper β RootApp.
Full detail on the architecture page.
/features
One _api + _impl pair per business domain
The template ships two, on purpose β counter is the
minimal shape modular new feature generates; posts
is the same shape with a full data layer over a real backend.
counter_api / counter_implβΆ
Two screens, one counter. This is the shape every generated feature starts from β deliberately kept small so the wiring is what stands out, not the feature.
Cross-feature link: counter_impl depends on
posts_api (never posts_impl) to push a
NavigateToPosts effect through PostsApi.launcher β
the worked example of leaving one feature for another. See it on the
architecture page.
posts_api / posts_implβΆ
A list screen and a details screen backed by
JSONPlaceholder β
GET /posts and GET /posts/{id}. This is the
template's one worked example of a data layer, laid out to copy
when a feature needs to talk to a backend.
Three rules hold it together:
The repository throws PostsFailure and nothing else
Mapping AppNetworkException happens once, exhaustively, over the sealed hierarchy β a new transport error fails to compile rather than quietly reading "something went wrong".
Failures reach the widget as values, not strings
A controller has no BuildContext, so it can't localize. PostsFailureL10n.message(context) turns the failure into a sentence at the only layer that can.
Controllers take dependencies in, never resolve them
PostsController({required this._repository}), with the screen passing context.locator() from AppStateProvider.create β trivially testable with a fake.
Registration lives in the module, under types
declared in lib/src/ β shared container, private contracts:
container ..registerLazySingleton<PostsRemoteDataSource>(...) ..registerLazySingleton<PostsRepository>(...) ..registerLazySingleton<PostsApi>((_) => const PostsApiImpl());
_api/_impl split is about module boundaries, not what
happens inside one. posts's domain/ + data/ layout is an
example, not a rule β nothing outside the package can see it either way,
so a feature is free to have fewer, more, or different layers.
/base
Cross-feature primitives
Not infrastructure (that's core), not a business domain
(that's features) β shared concerns every feature might touch:
translated strings, and this application's own network contract.
app_localizationβΆ
ARB-based translations with a locale scope in the widget tree and
a notifier in the container β so code without a BuildContext
(a network interceptor, for instance) can still read the selected
language.
Adding a language means two edits that must agree: a new
app_<code>.arb with every key from app_en.arb, and a
value on the AppLocale enum β supportedLocales derives
from that enum. A test fails if the two ever drift apart.
app_network_contractβΆ
This application's own success/error envelope, built on top of
the transport-agnostic network_api. It's the single seam
between a generic REST client and your backend's response
shape β see it worked through in detail below.
Registered once, at composition time:
NetworkModule<AppResponse>(responseParser: AppResponseParser()).
Everything below it β network_api, network_impl, every
feature β stays free of any envelope knowledge.
/core
Infrastructure β each its own api/impl pair
Swappable in principle: replace network_impl's
dio client, or router_impl's go_router, and
nothing above _api notices.
state_managerβΆ
A from-scratch State/Event/Effect implementation β no bloc, no
riverpod, no provider. AppStateController<S, E, F> is the
whole engine; see the loop diagram on the
architecture page.
routerβΆ
router_api defines routes, requests and the navigation
service purely in domain terms; router_impl maps them onto
go_router. No feature imports go_router β everything
goes through AppRouteRequest, AppModuleRouter and
context.navigation.
dependency_injectionβΆ
The abstraction every module registers through, independent of
any DI library β swapping get_it for something else is one
new _impl package.
networkβΆ
A REST client generic over the success model, so it carries
zero knowledge of any backend envelope β that knowledge
lives entirely in a NetworkResponseParser<TSuccess>, supplied
once at composition time by base/app_network_contract.
design_system (+ assets)βΆ
The token system in full β colour, spacing, radius, size, typography β but deliberately only six components. Build your own on the tokens rather than extending a component library that wasn't designed for your product.
storageβΆ
Two key/value contracts β StandardStorageApi for plain
preferences (theme, locale) and a secure counterpart for anything
sensitive β behind one StorageModule.
loggerβΆ
A talker-backed logger with an in-app log screen. The
network module's HTTP logging interceptor is built on the same
LoggerApi, so requests and app logs land in one place.
biometric_authβΆ
A small, two-method contract for device biometrics β availability and a single authenticate call β kept deliberately minimal so it's easy to see everywhere it's used.
app_linterβΆ
An analysis_options.yaml only β every other package
includes it with include: package:app_linter/analysis_options.yaml.
Lint rules change here and nowhere else, so the ruleset can never
quietly diverge between packages. Notably strict:
strict-casts, strict-inference, strict-raw-types,
require_trailing_commas, prefer_relative_imports,
implementation_imports: error.