Skip to main content

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-mWhat the module doesIn an app
App entryflutter_coreThe Flutter project: main(), start-up, root widget, native projectsalways, added by SMF
Routergo_routerRoutes of the features, typed navigationnone or one
Layoutbottom_tabsTabs at the bottom for the features' destinations, in the colors of the themenone or one
Settings screensettingsA screen with the settings of the modules in one group, a row for eachnone or one
Onboarding screenonboardingThe onboarding that a new user goes through on the first launch of the app, with the steps that other modules addnone or one
State managementblocBLoC with flutter_blocnone or one
riverpodRiverpod with flutter_riverpod
Themematerial_themeA light and a dark Material 3 theme with a palette and a bundled font, and the theme mode that the user selectsnone or one
App iconlauncher_iconsAn icon for iOS and Android, made from two images in the app that you replacenone or one
Splash screennative_splashThe 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 replacenone or one
Localizationgen_l10nThe texts of the app in ARB files, in the language of the device or in the one that the user chosenone or one
Dependency injectionget_itService locator with get_itnone or one
Eventsevent_busA service that delivers events between parts of the appnone or one
Preferencesshared_preferencesThe settings of the app (not secrets), remembered between its launchesnone or one
Notificationslocal_notificationsNotifications now and at about a time to come, the permission to show them, and the taps on themnone or one
Push messagesfirebase_messagingPush messages with Firebase Cloud Messaging: the address of the device for a backend, the messages and the tapsnone or one
REST APIdioA REST client with the address in .env, failures of a closed set, repeats, a cancel for each call and the token of the usernone or one
PurchasesrevenuecatPremium access with RevenueCat: the plans of the current offer, a purchase, a restore, and the access of the user before the first framenone or one
Values in .envdart_defineThe values that you fill in after generating the app, such as public API keys, given to the app with --dart-define-from-filenone or one
Crash reportingfirebase_crashlyticsReports errors to Firebase Crashlyticsany number
Analyticsfirebase_analyticsRecords events and screen views in Firebase Analyticsany number
Authenticationfirebase_authSigns users in with Firebase Authentication, with an email address and a password, as anonymous users, or with Google and Applenone or one
CIgithub_actionsThe format check, flutter analyze and flutter test on each push, and a release APK for an environment on requestnone or one

Six modules provide no role:

Kind-mWhat the module does
FeaturehomeA start screen at /home, which welcomes the developer of the app and lists the next steps
Featuresign_inThe 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
FeaturepaywallThe 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
Infrastructurefirebase_coreSets up Firebase, for the other Firebase modules
Infrastructuregoogle_sign_inSign-in with Google on Android and iOS, a way to sign in for the authentication
Infrastructureapple_sign_inSign 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_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.

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.

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.The screen of the account of sign_in: the address of the account, the button Sign out, and Delete account in red.

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 needsWhyWhat to add
A state managerThe 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 screenThe entry of the account is there. smf create adds settings by itself.nothing
A way to the settings screenA 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​

CommandWhat you get
smf create my_appEvery question, in a terminal.
smf create my_app --no-inputOnly what every app needs: a Flutter app with a fallback start screen.
smf create my_app -m home,bottom_tabsA start screen, the router and tabs for the screens that features add.
smf create my_app -m home,settings,bottom_tabs,shared_preferencesA 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_tabsThe 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_themeAll of that, and an onboarding on the first launch.
smf create my_app -m home,bottom_tabs,settings,onboarding,local_notificationsNotifications, 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,blocA 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_analyticsCrash reports and analytics in Firebase, with screen views.
smf create my_app -m home,sign_in,bloc,bottom_tabsSign-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 guestThe 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_themeAn 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_inThe 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_messagingPush 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,blocA 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,dioA 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_actionsA 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 anonymousThe 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.