# `flutter_core`

| Package | Kind | Provides the role | Needs | Choose with |
| --- | --- | --- | --- | --- |
| [`smf_flutter_core`](https://pub.dev/packages/smf_flutter_core) | scaffold | App entry | nothing | added to every app |

`flutter_core` creates the Flutter app that every SMF app starts from: the Android and iOS projects, `main()`, the start-up sequence, the root widget, `pubspec.yaml`, and, in an app that no route starts, the screen that the app starts on instead. It provides the app entry role, and every app needs exactly one provider of that role, so `smf create` adds `flutter_core` to every app.

## What it adds to the app

| File | What it holds |
| --- | --- |
| `lib/main.dart` | `main()`: `WidgetsFlutterBinding.ensureInitialized()`, `await bootstrap()`, then `runApp()` with the root widget inside the wrappers of other modules. |
| `lib/bootstrap.dart` | `bootstrap()`, where the other modules put their start-up code, phase by phase. It imports neither `material` nor `cupertino`. |
| `lib/app.dart` | `App`, the root widget: a `MaterialApp.router` with the router's configuration when the app has a router, or else a `MaterialApp` that shows the fallback start screen. `App` creates it in its `build`, with the arguments that modules and roles give it, such as the theme. Its `builder` holds the widgets that other modules put around the content of every route. |
| `lib/core/app/fallback_start_screen.dart` | Only in an app that no route starts: `FallbackStartScreen`, [the screen](#the-fallback-start-screen) that such an app starts on, and `FallbackStartView`, which the screen builds with its texts. |
| `test/core/app/fallback_start_screen_test.dart` | Only in an app that no route starts: a widget test of the fallback start screen. |
| `pubspec.yaml` | The dependencies of all modules, with Dart `^3.12.0` and Flutter `>=3.44.0`, and `flutter_lints` for the lints. |
| `analysis_options.yaml` | The lints of a new Flutter app, with `build/**` left out of the analysis. |
| `README.md` | The name of the app, the line "Generated with Say My Frame", and the sections that other modules add. |
| `.gitignore`, `.metadata` | The files of the same names that `flutter create` writes. |
| `android/`, `ios/` | The projects of `flutter create` of Flutter 3.44, with the identifiers of the app and the [flavors](https://doc.saymyframe.com/guides/flavors.md) `dev`, `staging` and `prod`. |

The native projects are those that `flutter create --platforms=android,ios` writes with Flutter 3.44.2, with a few changes:

- The names and identifiers come from the app. The Android namespace, application id and Kotlin package path, and the iOS bundle id, come from `--org` and the app name.
- The flavors are plain text, with no package. The Gradle file of the app has `flavorDimensions` and `productFlavors`, each with its id suffix and its name on the device. On iOS, the Xcode project has `Debug-<flavor>`, `Profile-<flavor>` and `Release-<flavor>` in each of its three configuration lists, and a shared scheme for each flavor. The Android label is the placeholder `${appName}` and the iOS display name the setting `APP_DISPLAY_NAME`, so each flavor has a name of its own: the name of the app in title case, with `Dev` or `Staging` after it, and without either for `prod`. The scheme `Runner` and the plain configurations stay, with the id and the name of `prod`.
- The minimum iOS version is 15.0, the lowest that Xcode 27 builds for, unless a module needs a higher one. The Xcode project has no development team.
- The Android manifest, `Info.plist`, `AppDelegate.swift` and the Gradle files have places where other modules add permissions, keys, components, code and plugins; see [the sockets of the app entry](https://doc.saymyframe.com/concepts/sockets.md#the-sockets-of-the-app-entry). A place that gets nothing leaves no line. So an app whose modules add nothing there has the files of `flutter create`, apart from two changes below: an empty `dependencies` block in the Gradle file of the app, and `Runner.entitlements`, which the Runner target signs with.
- The Gradle file of the app ends with a `dependencies` block for the dependencies of the modules, which stays empty when no module adds one. It also has the two places of core library desugaring: the switch in `compileOptions` and the library in `dependencies`.
- `ios/Runner/Runner.entitlements` holds the entitlements of the iOS app, such as Push Notifications or Sign in with Apple, which modules add. Every build configuration of the Runner target signs with the file, also while it is empty, as Xcode does when it adds a capability. With nothing in it, the file changes nothing of how the app is signed.

The app entry role adds two files to every app too, whichever module provides it: `lib/core/app/status_bar_icons.dart` with `StatusBarIcons`, which the root wraps around the content of every route, and its widget test, `test/core/app/status_bar_icons_test.dart`. The widget asks the system for dark icons of the status bar on a light theme and for light ones on a dark theme. A screen with an `AppBar` gets the icons that suit its bar. The test is in every app, so `flutter test` has a test to run in an app whose modules bring none.

The app also gets `AGENTS.md` and `CLAUDE.md`, the guide for coding agents. They come from the app entry role, whichever module provides it, so an app with another provider of the role has them too. To the section "App entry" of the guide, the module adds where each flavor is in the native projects, the commands to run after a change, the rules of its root widget with the one `MaterialApp` of the app, where the test of a file goes, and that the entitlements of the iOS app are in `ios/Runner/Runner.entitlements`, which the Signing & Capabilities tab of Xcode edits too. See [Working with a coding agent](https://doc.saymyframe.com/guides/coding-agents.md).

## The fallback start screen

An app has the fallback start screen only when no route can start it: an app without a router, which shows the screen as the home of its root, and an app with a router whose features mark no route to start on, where the screen is at `/`. It is a screen for you, the developer of the app. An app with a feature whose route can start it, such as [`home`](https://doc.saymyframe.com/modules/home.md), gets neither the screen nor its test nor its texts.

The screen shows, in the middle:

1. The app as a cell of the periodic table, with smaller cells around it. `smf create` makes the symbol and the number of the cell from the name of the app, `Ma` and 5 for `my_app`: the first letters of its first two words, or the first two letters of a name of one word, and how many letters and digits the name has.
2. The name of the app.
3. A hint: "No start screen yet. Add a feature with a route, or replace this screen."
4. The path of the file of the screen, `lib/core/app/fallback_start_screen.dart`. A tap copies it, and a snack bar says so.

| | |
| --- | --- |
| Colors and fonts | The screen takes its colors and its text styles from the theme of the app, so it follows the theme mode. The path and the number of the cell are in the monospaced font of the device. |
| Motion | The screen comes in with a short fade, once. On a device that asks for less motion, it is there at once. |
| Large text | The screen scrolls when it is too small for what it shows. The letters of the cell keep their size, since the cell is a picture, and the path grows to at most one and a half times its size. |
| Screen readers | A screen reader passes over the cell, and announces the name of the app as a header and the path as a button. |

### Its texts and the languages of the app

The hint and the word that the snack bar says before the path, Copied, are texts of the module, in English and in Ukrainian. In an app with a module that provides the localization, such as [`gen_l10n`](https://doc.saymyframe.com/modules/gen-l10n.md), they are the texts `flutterCoreFallbackHint` and `flutterCoreFallbackCopied` of the app, and the screen shows them in the language of the app. Without one, they are in English.

The module gives the localization role the two texts only in an app that has the screen. So such an app with a module that provides the localization is in English and in Ukrainian unless `--locales` says otherwise, also an app without a feature; see [Languages](https://doc.saymyframe.com/guides/languages.md#the-languages-of-the-app).

`FallbackStartScreen` only reads the two texts and gives them to `FallbackStartView`, which shows the rest. In an app with `gen_l10n`:

```dart title="lib/core/app/fallback_start_screen.dart"
class FallbackStartScreen extends StatelessWidget {
  /// Creates the screen.
  const FallbackStartScreen({super.key});

  @override
  Widget build(BuildContext context) => FallbackStartView(
    hint: context.l10n.flutterCoreFallbackHint,
    copied: context.l10n.flutterCoreFallbackCopied,
  );
}
```

So the widget test of the app shows the view with texts of its own, and needs neither the root of the app nor its texts:

```dart title="test/core/app/fallback_start_screen_test.dart"
/// Shows the view of the fallback start screen with texts of the test.
Future<void> _show(WidgetTester tester) => tester.pumpWidget(
  const MaterialApp(
    home: FallbackStartView(hint: 'A hint', copied: 'Copied'),
  ),
);
```

The test checks that the view names the app and the file, that a tap copies the path, and that the view fits a small phone with the largest text size.

### Give the app a start screen

In an app that shows the screen, replace it with the first screen of your own. The widget test is a test of `FallbackStartView`, so change it with the view, or remove it with the view, and with the two texts in an app with `gen_l10n`.

## Start-up phases

`bootstrap()` runs the start-up code that modules and roles add, in four phases, in this order:

| Phase | What goes there |
| --- | --- |
| early | Code that must come before anything else |
| platform | Platform services, such as `await Firebase.initializeApp(...)`, `installCrashReporting()` and `await initPreferences()` |
| DI | The services of the app: `await registerDependencies();` |
| late | Code that needs those services |

`main()` runs the root widget inside the wrappers that modules add, such as the `ProviderScope` of Riverpod. `App` builds below them, so an argument of the root `MaterialApp` can read an inherited widget among the wrappers from the `BuildContext` of `App`, and the root rebuilds when that widget notifies. The theme mode reaches the root this way.

## Choosing it

Every app has a provider of the app entry role. While `flutter_core` is the only module that provides it, you don't choose it: `smf create` adds it by itself.

## Requirements

The app needs Flutter 3.44 or newer and Dart 3.12 or newer, and runs on iOS 15 or newer. For iOS builds with Xcode 27, see [troubleshooting](https://doc.saymyframe.com/guides/troubleshooting.md#ios-builds-with-xcode-27).
