# Choosing modules

An app is made of the modules you ask for and the modules they need. You name modules with `-m` or pick them in the questions of `smf create`, and SMF adds the rest.

## Roles and the modules that provide them

Most modules provide a role, a part of the app that other modules count on without knowing which module provides it. An app has at most one provider of most roles, while crash reporting and analytics can have several.

| Role | `-m` | What the module does | In an app |
| --- | --- | --- | --- |
| App entry | `flutter_core` | The Flutter project: `main()`, start-up, root widget, native projects | always, added by SMF |
| Router | `go_router` | Routes of the features, typed navigation | none or one |
| Layout | `bottom_tabs` | Tabs at the bottom for the features' destinations, in the colors of the theme | none or one |
| Settings screen | `settings` | A screen with the settings of the modules in one group, a row for each | none or one |
| Onboarding screen | `onboarding` | The onboarding that a new user goes through on the first launch of the app, with the steps that other modules add | none or one |
| State management | `bloc` | BLoC with `flutter_bloc` | none or one |
| | `riverpod` | Riverpod with `flutter_riverpod` | |
| Theme | `material_theme` | A light and a dark Material 3 theme with a palette and a bundled font, and the theme mode that the user selects | none or one |
| App icon | `launcher_icons` | An icon for iOS and Android, made from two images in the app that you replace | none or one |
| Splash screen | `native_splash` | The screen that the system shows while the app starts, for the light and the dark mode, made from two images in the app that you replace | none or one |
| Localization | `gen_l10n` | The texts of the app in ARB files, in the language of the device or in the one that the user chose | none or one |
| Dependency injection | `get_it` | Service locator with get_it | none or one |
| Events | `event_bus` | A service that delivers events between parts of the app | none or one |
| Preferences | `shared_preferences` | The settings of the app (not secrets), remembered between its launches | none or one |
| Notifications | `local_notifications` | Notifications now and at about a time to come, the permission to show them, and the taps on them | none or one |
| Push messages | `firebase_messaging` | Push messages with Firebase Cloud Messaging: the address of the device for a backend, the messages and the taps | none or one |
| REST API | `dio` | A REST client with the address in `.env`, failures of a closed set, repeats, a cancel for each call and the token of the user | none or one |
| Purchases | `revenuecat` | Premium access with RevenueCat: the plans of the current offer, a purchase, a restore, and the access of the user before the first frame | none or one |
| Values in .env | `dart_define` | The values that you fill in after generating the app, such as public API keys, given to the app with `--dart-define-from-file` | none or one |
| Crash reporting | `firebase_crashlytics` | Reports errors to Firebase Crashlytics | any number |
| Analytics | `firebase_analytics` | Records events and screen views in Firebase Analytics | any number |
| Authentication | `firebase_auth` | Signs users in with Firebase Authentication, with an email address and a password, as anonymous users, or with Google and Apple | none or one |
| CI | `github_actions` | The format check, `flutter analyze` and `flutter test` on each push, and a release APK for an environment on request | none or one |

Six modules provide no role:

| Kind | `-m` | What the module does |
| --- | --- | --- |
| Feature | `home` | A start screen at `/home`, which welcomes the developer of the app and lists the next steps |
| Feature | `sign_in` | The screens of sign-in, sign-up and password reset for an email address and a password, with a button for each other way to sign in, and the screen of the account |
| Feature | `paywall` | The paywall, which sells premium access with the plans of the current offer, and the screen of the subscription, with a row of the settings screen and a step of the onboarding |
| Infrastructure | `firebase_core` | Sets up Firebase, for the other Firebase modules |
| Infrastructure | `google_sign_in` | Sign-in with Google on Android and iOS, a way to sign in for the authentication |
| Infrastructure | `apple_sign_in` | Sign in with Apple on iOS, a way to sign in for the authentication |

Each module has a [page of its own](https://doc.saymyframe.com/modules.md).

## What SMF adds for you

- `flutter_core` goes into every app.
- A module brings the modules it depends on: `firebase_crashlytics`, `firebase_analytics`, `firebase_messaging` and `firebase_auth` bring `firebase_core`.
- A module that requires a role brings its provider when only one module provides it. Features need a router, and so do the layout and the onboarding, so `home`, `settings`, `onboarding` or `bottom_tabs` brings `go_router`. A module that needs dependency injection brings `get_it`. `material_theme`, `gen_l10n`, `onboarding` and `local_notifications` each remember something between the launches of the app, so each brings `shared_preferences`. `firebase_messaging` brings `local_notifications`, through which a message shows in the foreground. `sign_in` brings `firebase_auth`, which keeps the accounts, and `settings`, where the entry of the account is. `google_sign_in` and `apple_sign_in` require sign-in too, so each brings `firebase_auth`. `revenuecat` reads its API keys from `.env`, so it brings `dart_define`, and `paywall` requires the purchases, so it brings `revenuecat` and `dart_define`. `dio` reads the address of the API from `.env`, so it brings `dart_define` too.

When several modules provide a required role, a run in a terminal asks which one to use. A run without a terminal stops and asks you to add one of them to `-m`. Before it generates the app, SMF prints each module it added and why.

## How the modules combine

### Every app has three flavors

Whatever its modules, an app has the flavors `dev`, `staging` and `prod`, which install next to each other, and `flutter run` builds `dev`. A module with a setup in a service, such as Firebase or RevenueCat, repeats that setup for each flavor that needs it. See [Flavors](https://doc.saymyframe.com/guides/flavors.md).

### Screens need a router

A feature declares its routes for whatever router the app has. An app without a router shows the [fallback start screen](https://doc.saymyframe.com/modules/flutter-core.md#the-fallback-start-screen), which names the app and tells you where its first screen goes, and so does an app with a router but no screen to start on. `smf create` warns of the latter, with the code `router.no_start`. Only such apps have the screen.

### The layout shows the destinations of the features

A feature can mark a route as a destination of the main navigation, as `home` does. With `bottom_tabs`, every destination becomes a tab, in the order the features were chosen. With one destination the tab bar is hidden, and without any the app has no main navigation. The bar holds at most five tabs. A feature whose destination does not fit is left out with a warning, and with `--strict` the run stops instead.

Without a layout, the app has no main navigation to lead to its destinations. `smf create` then warns of each destination other than the one that the app starts on, by its path and its module:

```text
[WARN] [role:router]: No module provides the layout role, so the app has no main navigation to lead to the destination /settings (settings). Choose a provider of the role, or open it from a screen of yours with context.nav.
```

The app is generated all the same. With `--strict`, the warning `router.no_main_navigation` stops the run instead; see [lenient and strict](https://doc.saymyframe.com/guides/smf-create.md#lenient-and-strict).

### The settings screen is a feature too

`settings` provides the settings screen role, and it is a feature with a route of its own, so it needs a router and its screen is a destination. With `bottom_tabs`, Settings becomes a tab next to the tabs of the other features. Without a layout, nothing that SMF generates opens the screen, and your own code opens it; see [`settings`](https://doc.saymyframe.com/modules/settings.md#opening-the-screen).

A module or a role with a setting gives the settings screen role a row, and the screen shows all of them, whichever module provides the screen. The theme mode of `material_theme` and the language of `gen_l10n` each have one, the notifications have one for the permission, `sign_in` has the row of the account, `paywall` the row of the subscription, and the purchases one that restores them. In an app without a settings screen, the mode and the language work without their rows. In an app with `settings` and no setting, the screen shows a note for you where the rows go.

### Preferences remember what the user chose

A module that keeps something the user chose saves it through the preferences role, which `shared_preferences` provides. The theme mode of `material_theme` and the language of `gen_l10n` are kept this way, and with `settings` the screen has a row for each. The preferences are for settings and not for secrets: nothing in them is encrypted, so a token, a password or a key never goes there. See [Preferences and settings](https://doc.saymyframe.com/guides/preferences.md).

### Texts follow the language of the app

A module gives its texts to the localization role, in English and in the other languages that it has them in. With `gen_l10n`, the app keeps them in ARB files and shows them in the language of the device, and you add your own texts and languages there. The built-in modules have their texts in English and in Ukrainian, and so has the fallback start screen of an app that no route starts, so an app with `gen_l10n` is in both languages unless `--locales` says otherwise. Without a module that provides the localization, the modules show their texts in English. See [Languages](https://doc.saymyframe.com/guides/languages.md).

### The onboarding keeps the user until it is finished

`onboarding` is a feature with a guard of the routes. On the first launch, the app shows the onboarding in place of every other screen, whichever features it has, and it remembers in the preferences that the onboarding is finished. It provides the onboarding screen role, so other modules add steps to it, such as the page of the notifications that asks for the permission and the page of the paywall that offers premium access. See [`onboarding`](https://doc.saymyframe.com/modules/onboarding.md).

### Notifications ask for the permission where the app can

`local_notifications` shows notifications through `appNotifications`, and `firebase_messaging` receives push messages through `appPush`, showing a message in the foreground as a notification. The app asks for the permission to show notifications on a page of the onboarding, in an app with one, and from a row of the settings screen, in an app with one. An app with neither asks nowhere until its code does. See [Notifications and push messages](https://doc.saymyframe.com/guides/notifications.md).

### Sign-in is screens, a session and a mode

`sign_in` is the screens: the sign-in, the sign-up, the password reset and the screen of the account. `firebase_auth` provides the auth role under them: `appSession`, which knows who uses the app before the first frame, on Firebase Authentication. `sign_in` requires that role, so it brings `firebase_auth`, and `firebase_core` with it.

![The sign-in of sign_in with the line Choose how to sign in to your account., the buttons Continue with Google and Continue with Apple, then or, the fields Email and Password, the button Sign in, and Create account below it.](https://doc.saymyframe.com/demo/0.8/app_sign_in.png)
![The screen of the account of sign_in: the address of the account, the button Sign out, and Delete account in red.](https://doc.saymyframe.com/demo/0.8/app_account.png)

`smf create` asks who may use the app without an account, or takes `--auth-mode`: nobody (`required`, the default), everyone (`guest`), or everyone as an anonymous user (`anonymous`). In `required` the app shows the sign-in until the user is signed in. In the other two it asks for an account only on the screens that need one.

An app with `sign_in` needs three more things:

| It needs | Why | What to add |
| --- | --- | --- |
| A state manager | The screens keep their state with it, in a variant for each. Two modules provide one, so `smf create` asks, or stops without a terminal. | `bloc` or `riverpod` |
| A settings screen | The entry of the account is there. `smf create` adds `settings` by itself. | nothing |
| A way to the settings screen | A main navigation shows the settings screen among its destinations. Without one, no screen leads to it, and a screen of yours opens it or the account. | `bottom_tabs` |

An app can also have `firebase_auth` without `sign_in`: the session and the mode, with screens of your own. See [Sign-in](https://doc.saymyframe.com/guides/sign-in.md).

`google_sign_in` and `apple_sign_in` add the ways to sign in with Google and Apple, which the screens of `sign_in` show as buttons. On iOS, App Store Review Guideline 4.8 asks an app with a login service of a third party, such as Google sign-in, to offer an equivalent way to sign in too, one that asks for no more than the name and the email address of the user and lets the user keep the address private. Sign in with Apple is such a way, and `smf create` warns of an app without one; see [ways to sign in](https://doc.saymyframe.com/guides/sign-in.md#ways-to-sign-in).

### The paywall sells what the purchases keep

`paywall` is the screens: the paywall and the screen of the subscription. `revenuecat` provides the purchases role under them: `appPurchases`, which knows before the first frame whether the user has premium access, and sells the plans of a RevenueCat offering. `paywall` requires that role, so it brings `revenuecat`, which brings `dart_define` for its API keys in `.env`.

Right after `smf create`, the keys are empty and the app runs without purchases. A debug build sells once the key of the Test Store of RevenueCat is in `.env`, and a release needs steps by hand in App Store Connect, Google Play Console and RevenueCat, which the README of the app lists. The screens keep their state with the state manager of the app, so an app with `paywall` has `bloc` or `riverpod`. An app can also have `revenuecat` without `paywall`, with screens of your own. See [Purchases](https://doc.saymyframe.com/guides/purchases.md).

### The REST client adds what the app has

`dio` provides the REST role: `appRest`, with its address in `.env`. No module of the CLI makes a request itself, so the client is there for your code. In an app with sign-in, it adds the token of the user to the requests to that address, whichever module provides the sign-in. See [REST API](https://doc.saymyframe.com/guides/rest.md).

### CI runs what the app has

`github_actions` provides the CI role, which runs the checks on each push and builds a release APK for one flavor on request. In an app with values in `.env`, a build writes its own `.env` from the variables of the environment of its flavor. See [CI](https://doc.saymyframe.com/guides/ci.md).

### The icon and the splash screen are yours to replace

`launcher_icons` and `native_splash` provide the app icon and the splash screen from images in `assets/branding/`, which show the mark of Say My Frame until you replace them. A run in a terminal that answers their questions with Enter gets both. A run with `--no-input`, or a `-m` without them, keeps the logo of Flutter and a white launch screen. See [App icon and splash screen](https://doc.saymyframe.com/guides/app-icon-and-splash.md).

### Guards come in the order of a first launch

A guard of the routes has a stage, so the app asks its guards in the order of a first launch whatever the order of the modules: the onboarding comes before a guard that asks who the user is. See [Guards of routes](https://doc.saymyframe.com/concepts/guards.md#the-order-of-the-guards).

### State management is per feature

`bloc` and `riverpod` add their package, and `riverpod` also puts its `ProviderScope` around the app. A feature that keeps state brings its Cubits or providers in a variant for each state manager. `sign_in` and `paywall` do, so an app with either has `bloc` or `riverpod`. `home` and `onboarding` keep what moves on their screens in the screens themselves, so they work with either of them or with none.

### Services work with or without a container

The events, analytics and crash reporting roles each generate a service interface and a function that returns the app's service. With `get_it`, the roles register their services in the container, and features take them from there. Without a container, the app calls the function. See [Services and state](https://doc.saymyframe.com/guides/services.md).

### Analytics follows the router

With a router, `firebase_analytics` logs a screen view for each screen the user sees. Without a router, it records only the events that the app logs itself.

### Reporting modules add up

With more than one module for crash reporting or analytics, the app's `CrashReporter` or `AnalyticsService` forwards every call to each of them on its own, so a module that fails affects no other. See [Services and state](https://doc.saymyframe.com/guides/services.md#the-services).

## Some useful sets

| Command | What you get |
| --- | --- |
| `smf create my_app` | Every question, in a terminal. |
| `smf create my_app --no-input` | Only what every app needs: a Flutter app with a fallback start screen. |
| `smf create my_app -m home,bottom_tabs` | A start screen, the router and tabs for the screens that features add. |
| `smf create my_app -m home,settings,bottom_tabs,shared_preferences` | A start screen and a settings screen as two tabs, and preferences for the settings that the app remembers. |
| `smf create my_app -m material_theme,gen_l10n,home,settings,bottom_tabs` | The same two tabs, with a light and a dark theme and the texts of the app in ARB files, in English and in Ukrainian. The settings screen has a row for the theme mode and one for the language, and the app remembers both. |
| `smf create my_app -m home,bottom_tabs,settings,onboarding,gen_l10n,material_theme` | All of that, and an onboarding on the first launch. |
| `smf create my_app -m home,bottom_tabs,settings,onboarding,local_notifications` | Notifications, with a page of the onboarding and a row of the settings screen that ask for the permission. |
| `smf create my_app -m home,bottom_tabs,get_it,event_bus,bloc` | A start screen with tabs, a service container, events between parts of the app, and BLoC. |
| `smf create my_app -m home,bottom_tabs,get_it,firebase_crashlytics,firebase_analytics` | Crash reports and analytics in Firebase, with screen views. |
| `smf create my_app -m home,sign_in,bloc,bottom_tabs` | Sign-in in front of a start screen with tabs: nobody uses the app without an account. The account is under the tab Settings, and the app signs in with Firebase Authentication. |
| `smf create my_app -m home,sign_in,riverpod,bottom_tabs --auth-mode guest` | The same screens with Riverpod, in an app that everyone may use. The sign-in opens when a user without an account taps the account under Settings. |
| `smf create my_app -m home,onboarding,sign_in,bloc,bottom_tabs,gen_l10n,material_theme` | An onboarding on the first launch, then the sign-in, with a light and a dark theme, in English and in Ukrainian. The settings screen has the rows Account, Language and Theme. |
| `smf create my_app -m home,sign_in,bloc,bottom_tabs,google_sign_in,apple_sign_in` | The sign-in with the buttons Continue with Google and, on iOS, Continue with Apple above the form. |
| `smf create my_app -m home,bottom_tabs,firebase_messaging` | Push messages with Firebase Cloud Messaging, and the notifications through which a message shows in the foreground. |
| `smf create my_app -m home,bottom_tabs,settings,onboarding,paywall,bloc` | A paywall that sells premium access with RevenueCat, the row Subscription and the row Restore purchases on the settings screen, and the page of the paywall in the onboarding. The API keys of RevenueCat and the addresses of the terms of use and the privacy policy go into `.env`. |
| `smf create my_app -m home,bottom_tabs,dio` | A start screen with tabs, and a REST client for the code that you add, with its address in `.env`. |
| `smf create my_app -m home,bottom_tabs,launcher_icons,native_splash,github_actions` | A start screen with tabs, an icon and a splash screen to replace with your own, and CI on GitHub Actions. |
| `smf create my_app -m home,bottom_tabs,firebase_auth --auth-mode anonymous` | The session without the screens: a start screen with tabs, and an anonymous user that the app signs in when it starts. The screens that sign a user in are yours to write. |

To see what a set of modules gives before anything is generated, add `--explain`. It prints the modules, the roles, the dependencies and what the machine is missing, and then stops. See the [`smf create` reference](https://doc.saymyframe.com/guides/smf-create.md#explain).

## Conflicts

Asking for two providers of a role that an app has at most one of, such as `-m bloc,riverpod`, stops `smf create` with a usage error that names both. Keep one of them.
