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.
What SMF adds for you
flutter_coregoes into every app.- A module brings the modules it depends on:
firebase_crashlytics,firebase_analytics,firebase_messagingandfirebase_authbringfirebase_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,onboardingorbottom_tabsbringsgo_router. A module that needs dependency injection bringsget_it.material_theme,gen_l10n,onboardingandlocal_notificationseach remember something between the launches of the app, so each bringsshared_preferences.firebase_messagingbringslocal_notifications, through which a message shows in the foreground.sign_inbringsfirebase_auth, which keeps the accounts, andsettings, where the entry of the account is.google_sign_inandapple_sign_inrequire sign-in too, so each bringsfirebase_auth.revenuecatreads its API keys from.env, so it bringsdart_define, andpaywallrequires the purchases, so it bringsrevenuecatanddart_define.dioreads the address of the API from.env, so it bringsdart_definetoo.
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.
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, 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:
[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.
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.
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.
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.
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.
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.
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.


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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.