Tour of a generated app
This tour uses the app from Create your first app:
smf create my_app --org com.example -m home,onboarding,bottom_tabs,settings,gen_l10n,material_theme,get_it,event_bus --no-input
The app has eleven modules: the eight in -m, and three that SMF added. go_router is there because home needs a router, shared_preferences because the onboarding, the theme and the language are remembered, and flutter_core because every app has it.
What you see when it runs
On its first launch, the app shows the onboarding: a welcome page and a second page, each a cell of the periodic table with a title and a text below it. Skip is above the pages and Next below them, and on the last page the button is Get started. Once the onboarding is finished, the app shows the home screen, which welcomes the developer of the app and lists three next steps. At the bottom are two tabs, Home and Settings. The settings screen has a large title and two settings in one group: Language, a row that opens a sheet with the languages, and Theme, with a segment for each mode. The app is in English or in Ukrainian, in the language of the device, until the user chooses one of the two under Language.
Later launches open on the home screen, in the theme mode and the language that the user chose.



The files
my_app/
├── .gitignore
├── .metadata
├── AGENTS.md
├── CLAUDE.md
├── README.md
├── analysis_options.yaml
├── l10n.yaml
├── pubspec.yaml
├── android/ Android project of flutter create
├── assets/fonts/geist/ the font of the themes, with its license
├── ios/ iOS project of flutter create
├── lib/
│ ├── main.dart
│ ├── bootstrap.dart
│ ├── app.dart
│ ├── core/
│ │ ├── app/fallback_start_screen.dart
│ │ ├── di/
│ │ │ ├── service_locator.dart
│ │ │ └── dependencies.dart
│ │ ├── events/
│ │ │ ├── communication_service.dart
│ │ │ └── event_bus_communication_service.dart
│ │ ├── l10n/
│ │ │ ├── app_locale.dart
│ │ │ ├── language_setting.dart
│ │ │ └── l10n.dart
│ │ ├── layout/
│ │ │ ├── destination.dart
│ │ │ └── app_shell.dart
│ │ ├── preferences/
│ │ │ ├── app_preferences.dart
│ │ │ └── shared_app_preferences.dart
│ │ ├── router/
│ │ │ ├── app_router.dart
│ │ │ ├── navigation.dart
│ │ │ └── app_router_factory.dart
│ │ └── theme/
│ │ ├── app_theme.dart
│ │ ├── theme_mode.dart
│ │ └── theme_mode_setting.dart
│ ├── features/
│ │ ├── home/
│ │ │ ├── assets/ the image of the home screen
│ │ │ └── home_screen.dart
│ │ ├── onboarding/
│ │ │ ├── onboarding_screen.dart
│ │ │ ├── onboarding_pages.dart
│ │ │ └── onboarding_status.dart
│ │ └── settings/settings_screen.dart
│ └── l10n/
│ ├── app_en.arb
│ └── app_uk.arb
└── test/
└── core/app/fallback_start_screen_test.dart
flutter pub get adds pubspec.lock and .dart_tool/, as in any Flutter project. In this app it also generates lib/l10n/app_localizations.dart and a file for each language next to it, from the ARB files, and adds build/, where Flutter keeps the records of that step.
Every file has exactly one author. Some files come from a module, and others come from a role, whichever module provides it. A role describes what the rest of the app can count on, such as a router with typed navigation, and generates that part itself. The module that provides the role fills in the rest.
| Files | Written by |
|---|---|
main.dart, bootstrap.dart, app.dart, core/app/, test/, pubspec.yaml, analysis_options.yaml, README.md, .gitignore, .metadata, android/, ios/ | the module flutter_core, which provides the app entry |
AGENTS.md, CLAUDE.md | the app entry role |
core/router/app_router.dart, core/router/navigation.dart | the router role |
core/router/app_router_factory.dart | the module go_router, which provides the router |
core/layout/destination.dart | the layout role |
core/layout/app_shell.dart | the module bottom_tabs, which provides the layout |
core/preferences/app_preferences.dart | the preferences role |
core/preferences/shared_app_preferences.dart | the module shared_preferences, which provides the preferences |
core/l10n/app_locale.dart, core/l10n/language_setting.dart | the localization role |
core/l10n/l10n.dart, l10n/*.arb, l10n.yaml | the module gen_l10n, which provides the localization |
core/theme/theme_mode.dart, core/theme/theme_mode_setting.dart | the theme role |
core/theme/app_theme.dart, and assets/fonts/geist/ at the root of the app | the module material_theme, which provides the theme |
core/di/service_locator.dart | the dependency injection role |
core/di/dependencies.dart | the module get_it, which provides dependency injection |
core/events/communication_service.dart | the events role |
core/events/event_bus_communication_service.dart | the module event_bus, which provides the events |
features/home/, features/onboarding/ | the features home and onboarding |
features/settings/settings_screen.dart | the module settings, a feature that provides the settings screen |
This split lets modules replace each other. Code that navigates uses navigation.dart of the router role and not go_router, code that needs a service uses service_locator.dart and not get_it, and code that remembers a setting uses app_preferences.dart and not the shared_preferences package.
Start-up
lib/main.dart has the same three steps in every app. Modules and roles can wrap the root widget, and here the localization role and the theme role each put a scope around it, through which the root reads the language and the theme mode:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await bootstrap();
runApp(
AppLocaleScope(
notifier: appLocale,
child: AppThemeModeScope(notifier: appThemeMode, child: const App()),
),
);
}
bootstrap() runs the start-up code of modules and roles before the first frame. They add their code to one of four phases, which run in this order: code that must come first, platform services such as Firebase, the services of the app, and code that needs those services. In this app, the preferences role opens the preferences in the platform phase, and the dependency injection role registers the services after it:
Future<void> bootstrap() async {
await initPreferences();
await registerDependencies();
}
initPreferences() also gives the preferences to the code that keeps a setting there. So the theme mode, the language and whether the onboarding is finished have their saved values before the first frame.
With firebase_crashlytics, the platform phase would also initialize Firebase and install the crash reporting.
lib/app.dart holds the root widget. With a router it is a MaterialApp.router with the router's configuration. Without one, it is a MaterialApp that shows the fallback start screen of core/app/. The theme role and the localization role give it its other arguments:
Widget build(BuildContext context) => MaterialApp.router(
title: 'My App',
theme: createLightTheme(context),
darkTheme: createDarkTheme(context),
themeMode: AppThemeModeScope.of(context),
locale: AppLocaleScope.of(context),
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [...appLocales],
routerConfig: appRouter.config,
builder: (context, child) => Builder(
builder: (context) => AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarBrightness: Theme.of(context).brightness,
statusBarIconBrightness:
Theme.of(context).brightness == Brightness.dark
? Brightness.light
: Brightness.dark,
),
child: child!,
),
),
);
The builder of the root widget wraps the content of every route. The widget there comes from the app entry role, in every app: it tells the system whether the status bar needs dark or light icons, by the theme, so the status bar can be read over a screen without an app bar. A screen with an AppBar gets the icons that suit its bar. Modules put wrappers of their own there through the socket appBuilder.
Router and navigation
The router role generates two files. app_router.dart has the AppRouter interface and the instance appRouter, which createAppRouter() creates on first use. navigation.dart has the typed navigation: a location class for every route, such as HomeHomeLocation for /home, and context.nav, so a screen navigates with context.nav.home.home().go().
go_router implements createAppRouter() in app_router_factory.dart with a GoRouter that has every route of every feature, the redirect of / to the start screen and, with a layout, the tabs. The routes of each feature live under its id, so the route / of home is /home. See Navigation.
The onboarding declares a guard, and in an app with guards app_router.dart also has routeGuards. While the onboarding is not finished, the router shows /onboarding in place of every other location. See Guards.
Layout
The layout role generates destination.dart with Destination, which holds an icon and a function that returns the label, and appDestinations, the destinations of the app:
const List<Destination> appDestinations = [
Destination(label: _homeHomeLabel, icon: Icons.home),
Destination(label: _settingsSettingsLabel, icon: Icons.settings),
];
String _homeHomeLabel(BuildContext context) => context.l10n.homeLabel;
String _settingsSettingsLabel(BuildContext context) =>
context.l10n.settingsTitle;
bottom_tabs generates AppShell, a Scaffold with the screen of the selected tab and a bar with a tab for each destination, which the file draws itself in the colors of the theme. The router gives it appDestinations, in the order of the features, and keeps the stack of each tab while another one is selected. A label is a text of the feature of its tab, so the tabs follow the language of the app.
Preferences and settings
The preferences role generates app_preferences.dart with the AppPreferences interface, and shared_preferences implements it on the shared_preferences package. Code that keeps a setting gives the role a restorer, a function that reads the setting when the app starts. This app has three:
final List<void Function(AppPreferences preferences)> _restorers = [
restoreOnboarding,
restoreAppLocale,
restoreAppThemeMode,
];
settings generates the settings screen, which shows the entries that the roles and the modules of the app give it, in one group below its title. Here the localization role gives the entry Language and the theme role the entry Theme, in the order of the modules that provide the two roles. See Preferences and settings.
Texts and languages
Each module and role gives its texts to the localization role, in English and in the languages that it has them in. gen_l10n keeps them in ARB files, one for each language, from which Flutter generates the code that reads them. The texts of this app are in English and in Ukrainian: the label of the tab Home and the texts of the home screen, the texts of the onboarding, the title of the settings screen and the texts of its entries, and the two texts of the fallback start screen.
app_locale.dart of the role has the languages of the app, appLocales, and the language that the user chose, appLocale. Code reads a text as context.l10n.<name>. See Languages.
Theme
material_theme generates app_theme.dart with the light and the dark theme of the app: their colors, their text styles and the look of their components. It is the file to change for another look. The themes are in the font Geist, whose files the module puts into assets/fonts/geist/ and declares in pubspec.yaml, so the app loads no font from the network. The theme role generates theme_mode.dart, which keeps the mode that the user selected, and the row Theme of the settings screen. See Theme.
Dependency injection
The dependency injection role generates service_locator.dart with the ServiceLocator interface and the function resolve<T>(). get_it generates dependencies.dart, whose registerDependencies() registers the services of all modules in get_it:
Future<void> registerDependencies() async {
final getIt = GetIt.instance;
getIt.registerLazySingleton<di0.CommunicationService>(
() => di0.createCommunicationService(),
);
getIt.registerLazySingleton<di1.AppPreferences>(
() => di1.createAppPreferences(),
);
}
The events role and the preferences role registered their services here because the app has a container. Without one, the app would get the event service from createCommunicationService(). See Services and state.
Events
The events role generates communication_service.dart with AppEvent, the base class of events, and the CommunicationService interface. Its fire(event) sends an event, and on<T>() streams the events of a type. event_bus implements the interface on the EventBus of the event_bus package.
The features
Features keep their files in lib/features/<id>/:
homeaddshome_screen.dart, a welcome to the developer of the app that names the app and lists the next steps, and the image that the screen shows, inassets/next to it. Replace the content of the screen with the first screen of your app. The module declares one route,/home, which is a destination labelled Home and a screen that the app can start on. Seehome.onboardingadds the screen of the onboarding at/onboarding, its pages and its status. Replace the pages with your own inonboarding_pages.dart. Seeonboarding.settingsadds the settings screen at/settings, which is a destination too.
The rest of the project
pubspec.yamlhas the dependencies of all modules:event_bus,get_it,go_router,intlandshared_preferenceshere, next to Flutter's own. Itsfluttersection declares the image of the home screen, and the font of the themes with its license. The app needs Flutter 3.44 or newer and Dart 3.12 or newer.analysis_options.yamlhas the lints of a new Flutter app,flutter_lints, and leavesbuild/out of the analysis. On macOS, Flutter copies the Swift packages of plugins there, with their examples, andflutter analyzewould report those otherwise.android/andios/are the projects offlutter createof Flutter 3.44, with the app's identifiers:com.example.my_appon Android andcom.example.my-appon iOS. The minimum iOS version is 15.0.README.mdhas the name of the app. Modules and roles add sections to it. Heregen_l10ntells where the texts are, under "Texts", and the localization role how to add a language, under "Languages".test/has a widget test of the fallback start screen,lib/core/app/fallback_start_screen.dart. An app shows that screen while it has no screen to start on. This app starts on/homeand never shows it, but every app has the file and its test; seeflutter_core.
The guide for coding agents
AGENTS.md tells coding agents what the code of the app does not show, and CLAUDE.md holds one line, @AGENTS.md, for the agents that read that file instead. The app entry role generates both in every app, whichever module provides it. The guide has a section for each part of the app that has notes for agents. In this app those are the app entry, the dependency injection, the events, the home screen, the layout, the localization, the onboarding, the preferences, the router, the settings screen and the theme. The section of the router, for one, tells an agent to navigate with context.nav and names the three places that a route is in. See Working with a coding agent.
It is your code now
SMF generates the app once, and the result is a plain Flutter project. It has no SMF package in its dependencies, and no command updates it later, so change any file as you would in a project you wrote yourself. To add a screen later, see Routes of your own.
Next
- Choosing modules shows what else a generated app can have, such as the screens of sign-in.
- The module model explains how the modules fit together without knowing each other.