# Tour of a generated app

This tour uses the app from [Create your first app](https://doc.saymyframe.com/getting-started/first-app.md):

```bash
smf create my_app --org com.example -m home,bottom_tabs,settings,onboarding,gen_l10n,material_theme,get_it,event_bus,launcher_icons,native_splash --no-input
```

The app has thirteen modules: the ten 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 first page of the onboarding, with the symbol of the app in a cell.](https://doc.saymyframe.com/demo/0.8/app_1_onboarding_welcome.png)
![The home screen in the light theme and in English, with Good afternoon above the name of the app, above the tabs Home and Settings.](https://doc.saymyframe.com/demo/0.8/app_4_home.png)
![The home screen in the dark theme and in Ukrainian, with Добрий день above the name of the app, after the user chose both under Settings.](https://doc.saymyframe.com/demo/0.8/app_9_home_dark_uk.png)

## The files

```text
my_app/
├── .gitignore
├── .metadata
├── AGENTS.md
├── CLAUDE.md
├── README.md
├── analysis_options.yaml
├── flutter_launcher_icons.yaml
├── flutter_native_splash.yaml
├── l10n.yaml
├── pubspec.yaml
├── android/                                  Android project of flutter create, with the flavors
├── assets/
│   ├── branding/                             the images of the icon and the splash screen
│   └── fonts/geist/                          the font of the themes, with its license
├── ios/                                      iOS project of flutter create, with the flavors
├── lib/
│   ├── main.dart
│   ├── bootstrap.dart
│   ├── app.dart
│   ├── core/
│   │   ├── app/status_bar_icons.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
│   │   ├── onboarding/onboarding_steps.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/status_bar_icons_test.dart
└── tool/
    └── update_app_icon.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`, `pubspec.yaml`, `analysis_options.yaml`, `README.md`, `.gitignore`, `.metadata`, `android/`, `ios/` | the module `flutter_core`, which provides the app entry |
| `AGENTS.md`, `CLAUDE.md`, `core/app/status_bar_icons.dart`, `test/` | 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 |
| `assets/branding/icon.png`, `assets/branding/icon_foreground.png`, `flutter_launcher_icons.yaml`, `tool/update_app_icon.dart` | the module `launcher_icons`, which provides the app icon |
| `assets/branding/splash_light.png`, `assets/branding/splash_dark.png`, `flutter_native_splash.yaml` | the module `native_splash`, which provides the splash screen |
| `core/layout/destination.dart` | the layout role |
| `core/layout/app_shell.dart` | the module `bottom_tabs`, which provides the layout |
| `core/onboarding/onboarding_steps.dart` | the onboarding screen role |
| `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/` | the feature `home` |
| `features/onboarding/` | the module `onboarding`, a feature that provides the onboarding screen |
| `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:

```dart title="lib/main.dart"
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:

```dart title="lib/bootstrap.dart"
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.

```mermaid
sequenceDiagram
    participant main as main()
    participant boot as bootstrap()
    participant app as App
    main->>main: WidgetsFlutterBinding.ensureInitialized()
    main->>boot: await bootstrap()
    Note over boot: early phase
    Note over boot: platform phase: await initPreferences()
    Note over boot: DI phase: await registerDependencies()
    Note over boot: late phase
    boot-->>main: done
    main->>app: runApp(wrappers of the modules around App)
    app->>app: MaterialApp.router(routerConfig: appRouter.config)
```

`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](https://doc.saymyframe.com/modules/flutter-core.md#the-fallback-start-screen), which only an app that no route starts has. The theme role and the localization role give it its other arguments:

```dart title="lib/app.dart"
  @override
  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) => StatusBarIcons(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: `StatusBarIcons` of `lib/core/app/status_bar_icons.dart` 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](https://doc.saymyframe.com/concepts/sockets.md#the-sockets-of-the-app-entry) `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](https://doc.saymyframe.com/guides/navigation.md).

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](https://doc.saymyframe.com/guides/navigation.md#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:

```dart title="lib/core/layout/destination.dart"
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:

```dart title="lib/core/preferences/app_preferences.dart"
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](https://doc.saymyframe.com/guides/preferences.md).

## 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, and the title of the settings screen and the texts of its entries.

`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](https://doc.saymyframe.com/guides/languages.md).

## 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](https://doc.saymyframe.com/guides/theme.md).

## 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:

```dart title="lib/core/di/dependencies.dart"
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](https://doc.saymyframe.com/guides/services.md).

## 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](https://pub.dev/packages/event_bus) package.

## The features

Features keep their files in `lib/features/<id>/`:

- `home` adds `home_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, in `assets/` 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. See [`home`](https://doc.saymyframe.com/modules/home.md).
- `onboarding` adds the screen of the onboarding at `/onboarding`, its pages and its status. Replace the pages with your own in `onboarding_pages.dart`. The module provides the onboarding screen role, whose template writes `lib/core/onboarding/onboarding_steps.dart` with the steps that other modules add to the onboarding, such as a page that asks for the permission to show notifications. In this app the list is empty. See [`onboarding`](https://doc.saymyframe.com/modules/onboarding.md).
- `settings` adds the settings screen at `/settings`, which is a destination too.

## The rest of the project

- `pubspec.yaml` has the dependencies of all modules: `event_bus`, `get_it`, `go_router`, `intl` and `shared_preferences` here, next to Flutter's own. `shared_preferences` also keeps `shared_preferences_android` at 2.5.2 or later; see [`shared_preferences`](https://doc.saymyframe.com/modules/shared-preferences.md). Its `flutter` section 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.yaml` has the lints of a new Flutter app, `flutter_lints`, and leaves `build/` out of the analysis. On macOS, Flutter copies the Swift packages of plugins there, with their examples, and `flutter analyze` would report those otherwise.
- `android/` and `ios/` are the projects of `flutter create` of Flutter 3.44, with the app's identifiers, `com.example.my_app` on Android and `com.example.my-app` on iOS, and the [flavors](https://doc.saymyframe.com/guides/flavors.md) `dev`, `staging` and `prod`, which add `.dev` and `.staging` to them. `flutter run` builds `dev`.
- `launcher_icons` and `native_splash` add the four images of `assets/branding/`, `flutter_launcher_icons.yaml`, `flutter_native_splash.yaml` and `tool/update_app_icon.dart`, and their tools make the icon and the launch screen in the native projects. See [App icon and splash screen](https://doc.saymyframe.com/guides/app-icon-and-splash.md). The minimum iOS version is 15.0. The iOS app signs with `ios/Runner/Runner.entitlements`, which is empty in this app.
- `README.md` has the name of the app and the line "Generated with Say My Frame", which links to this site. The line lets a search on GitHub find the apps that Say My Frame generated, and you can remove it. Modules and roles add sections to the README. Here `gen_l10n` tells where the texts are, under "Texts", and the localization role how to add a language, under "Languages". The README of an app with steps that only you can do, such as uploading a key to the Firebase console, opens with the section "Finish the setup": the steps in order, with a link to their [guides](https://doc.saymyframe.com/setup.md). This app has none.
- `test/` has a widget test of `StatusBarIcons`, which every app has. An app that no route starts also has the fallback start screen and a test of it. This app starts on `/home`, so it has neither; see [`flutter_core`](https://doc.saymyframe.com/modules/flutter-core.md#the-fallback-start-screen).

## 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 onboarding screen, 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](https://doc.saymyframe.com/guides/coding-agents.md).

## 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](https://doc.saymyframe.com/guides/navigation.md#routes-of-your-own).

## Next

- [Choosing modules](https://doc.saymyframe.com/guides/choosing-modules.md) shows what else a generated app can have, such as the screens of [sign-in](https://doc.saymyframe.com/guides/sign-in.md).
- [The module model](https://doc.saymyframe.com/concepts/module-model.md) explains how the modules fit together without knowing each other.
