Skip to main content

The smf create command

smf create <app name> [options]

smf create generates a Flutter app from modules in a new directory. Besides smf help, it is the only command of smf. smf --version prints the version, and smf create --help lists the options.

Options​

OptionWhat it does
-m, --modules=<module,module>The modules to add, separated by commas, such as -m home,bottom_tabs. The modules they need are added too. Without it, a run in a terminal asks for the modules.
--org=<com.example>The organization in reverse domain notation. It starts the identifiers of the app. Without it, a run in a terminal asks; otherwise it is com.example.
-o, --output=<directory>The directory in which the app's own directory is created. The default is the current directory.
--on-conflict=<prompt|replace|copy|cancel>What to do when the app's directory exists and is not empty; see below. The default is prompt.
--strictStop instead of leaving out a module that cannot work, and on a warning about what the app is made of; see lenient and strict.
--explainPrint what would be generated and whether the machine is ready, then stop without changing anything; see below.
--skip-external-setupNever install tools, log in or configure external services; print what to run instead.
--[no-]inputWhether the run may ask questions in the terminal. With --no-input, everything comes from the options.
--start=<path>The full path of the screen the app starts on, such as /home. It can name any route without required parameters that is outside the flow of a guard and asks for no condition, and a run without a terminal needs it when several screens can start the app. An option of the router role.
--auth-mode=<required|guest|anonymous>Who may use the app without an account: nobody (required, the default), everyone (guest), or everyone as an anonymous user (anonymous); see Sign-in. An option of the auth role.
--locales=<codes>The languages of the app among those of the texts of its modules, such as en,uk. The first is the one the app uses when the device asks for none of them. The default is all of them, English first; see Languages. An option of the localization role.
--verboseReport more of what happens. It can come before or after create.
-h, --helpPrint the usage of the command.

The name and the identifiers​

The app name becomes the Dart package name of the app in snake_case, so My App, myApp and my-app all become my_app. The package name is also the name of the app's directory under --output. The package name must start with a Latin letter and contain only letters, digits and underscores. It cannot be a keyword of Dart, Java or Kotlin, or the name of a package every Flutter app depends on, such as flutter or collection.

Every part of the organization starts with a letter and has only letters, digits, hyphens and underscores, and no part is a keyword of Java or Kotlin. The identifiers of the app join the organization and the package name:

smf create my_app --org com.example
Android application id and namespacecom.example.my_app
iOS bundle idcom.example.my-app (bundle ids take hyphens, not underscores)

These are the ids of the flavor prod. The flavors dev and staging add .dev and .staging, so all three install next to each other; see Flavors.

Questions​

smf create asks questions only when it runs in a terminal, without --no-input and without --explain. It asks for:

  1. the app name, unless it is given;
  2. what to do with an existing directory, unless --on-conflict says;
  3. the organization, unless --org gives it;
  4. the modules, unless -m gives them: first the modules without a role by kind, such as the features, with home selected already, then a provider for each role, where Enter takes the first one (see Create your first app and the order of the roles);
  5. the provider of a role that the chosen modules require and that several modules provide, as the state manager for the screens of sign_in. Such a question names the module that requires the role and offers no None;
  6. whether to set up what a check of the machine found missing, such as the Firebase CLI, unless the run skips external setup (see the machine and external setup);
  7. what the roles of the app leave open: the start screen, when several screens can start the app and --start does not say, and who may use the app without an account, in an app with a module that provides sign-in, unless --auth-mode says;
  8. whether to run a step of a module after generation now, such as the tool that makes the app icon, or dart tool/configure_firebase.dart dev and after it the step that enables the sign-in methods in Firebase.

Without a terminal, or with --no-input, the run asks nothing. Where it would have to ask, it stops with a usage error that says what to add: the app name, --on-conflict, a provider in -m, or --start. This is the error of -m home,sign_in without a state manager:

Several modules provide the state management role, which sign_in requires: bloc, riverpod. Add one of them to -m.

It does not stop for --auth-mode: without it, the app is in the mode required. Without -m, the app gets only what every app needs: a run without a terminal never takes a module that it was not given, so it does not take home either.

The order of the roles​

The question of each role comes after the questions of the roles that require it, so that a role that an earlier answer needs offers no None. A role that an earlier answer requires and that only one module provides gets no question at all: smf create adds that module and says why. The questions come in this order, and a run asks those that the answers before leave open:

  1. Features, and Infrastructure: the modules without a role.
  2. Layout, Settings screen and Onboarding screen. Each requires the router, so the router has no question once one of them is chosen.
  3. Router, when no earlier answer requires it.
  4. Localization and Theme.
  5. Push messages, and then Notifications, which push messages require.
  6. Preferences, Dependency injection, State management and Events.
  7. Authentication, and then REST API, which uses it.
  8. Purchases, and then Values in .env, which the modules of the two roles before it read.
  9. Crash reporting and Analytics, each of which takes several modules.
  10. App icon, Splash screen and CI.

So the questions go from what the user of the app sees to what gets the app released. Enter takes the first module of a question that takes one answer. So a run in a terminal that answers every question with Enter gets a provider for each of those roles, among them the app icon and the splash screen.

The notifications role adds a step to the onboarding and an entry to the settings screen, so both screens are decided by the time of its question. In an app with every module, the onboarding, the theme, the localization and the notifications require the preferences, so shared_preferences comes without a question:

Adding shared_preferences: the only provider of the preferences role, which onboarding requires.

An existing directory​

When the app's directory exists and is not empty, --on-conflict decides:

ValueWhat happens
promptA run in a terminal asks what to do. The default answer keeps the directory and creates the app next to it; the others replace the directory or cancel the run. Without a terminal, the run stops with a usage error.
replaceSMF generates the app first. Then it sets the old directory aside, moves the app into its place and deletes the old directory. If moving the app fails, the old directory goes back.
copyThe app goes into a new directory next to it: my_app copy, or my_app copy 2 if that exists too.
cancelNothing is generated, and smf create exits with code 1.

SMF decides this before it does anything else, such as installing a tool.

Lenient and strict​

By default, smf create is lenient. When a module cannot work in the app, such as a feature whose tab does not fit into the tab bar, it leaves that module out with a warning, plans the app again without it, and generates the rest. At the end it warns again about the modules the app is without.

A run can also warn about what the app is made of while every module stays in it, such as an app whose routes mark none as a start screen. A lenient run prints such a warning and goes on, with the role that reported it:

[WARN] [role:router]: No route is marked as a start candidate, so the app starts on its fallback screen. Choose a start route with --start.

With --strict, a problem of a module stops the run with exit code 1 before anything is generated, and so does a warning about what the app is made of. A warning of the checks of the module model stops the run before the machine is checked, and a warning of a choice of a role, such as the start screen, stops it before the app is rendered. Use --strict in scripts and CI, where an app without a module you asked for, or an app that does not come out as you asked, is a failure. This is a strict run of an app with settings alone, which has no screen to start on and no main navigation that leads to the settings screen:

Generation stopped because of 2 warnings about the app. Without --strict, the run goes on after them.
warning [role:router] router.no_start: No route is marked as a start candidate, so the app starts on its fallback screen. Choose a start route with --start.
warning [role:router] router.no_main_navigation: 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.

A warning about the machine, such as a tool that a step after generation needs, never stops a run, strict or not.

Each warning about the app has a code: the id of the role that reports it, a dot, and a name. The built-in roles report these:

CodeWhat the app is made of
router.no_startThe app has routes, but no route can start it, and --start names none. It starts on the fallback start screen.
router.no_main_navigationThe app has destinations besides the one that it starts on, but no layout, so no main navigation leads to them.
router.ambiguous_routesTwo routes of a module both match some locations, which go to the route declared first.
localization.untranslatedTexts of a module have no translation into a language of the app, which shows them in English there.
localization.unsupported_languageA text of a module has a translation into a language that no app can be in, so the app leaves that language out.
localization.unnamed_languageThe setting of the language shows a language of the app by its code, since the role has no name for it.
auth.open_account_routesRoutes ask for an account, but no guard of the app stands for it, so every user gets to them; see Sign-in.
auth.no_equivalent_sign_inOn iOS, the app offers a sign-in of a third party, such as Google, and no way to sign in that the App Store takes as its equivalent, as its guideline 4.8 asks; see Sign-in.
pipeline.unused_optionAn option of a role that the app does not have, which nothing reads.

smf create has a hidden option for apps that warn on purpose, as some apps of SMF's own CI do: with --strict, --allow-warning=<code> lets the run go on after a warning with that code, which it still prints. Give it once for each code. A value that is not the form of a code is a usage error.

Some problems stop the run in either mode. A problem that no single module causes, such as a usage error, is one of them. So is the problem of a module that no app can be made without, such as flutter_core, the only provider of the app entry. smf create does not leave such a module out, and stops with its problem and how to fix it, such as a Flutter SDK that is too old.

Explain​

--explain runs everything that decides the app, which means the selection of modules, the checks of the module model and the checks of the machine, and prints the result. It never asks, installs or logs in, and it generates no file. Where a real run would ask which module provides a role, it takes the first one and names the others. It makes no choices either, so it shows the warnings of the checks of the module model and not those of the choices, and with --strict it stops only on the first.

smf create my_app --org com.example -m home,bottom_tabs,get_it,event_bus --explain
App my_app of com.example
Android application id: com.example.my_app
iOS bundle id: com.example.my-app
Directory: /Users/you/projects/my_app

Modules
home: requested
bottom_tabs: requested
get_it: requested
event_bus: requested
flutter_core: the only provider of the app entry role, which every app needs
go_router: the only provider of the router role, which home requires

Roles
Layout: bottom_tabs
Dependency injection: get_it
Events: event_bus
App entry: flutter_core
Router: go_router

Dependencies
get_it ^9.3.0 (get_it)
event_bus ^2.0.1 (event_bus)
flutter from the flutter SDK (flutter_core)
go_router ^17.5.0 (go_router)

Dev dependencies
flutter_test from the flutter SDK (flutter_core)
flutter_lints ^6.0.0 (flutter_core)

Machine
✓ Flutter SDK

Depending on the app, the report has more sections. "Left out (lenient mode)" lists the modules that the app would be without, and why. "Order of contributions" shows, for code that several modules put into the same place, the order they come in and the reason for it. "After generation" lists the commands that would run in the new app, such as dart tool/configure_firebase.dart dev of the Firebase modules, each step that continues another under it, and the notice of a step that has one. This is that section for an app with sign_in:

After generation
dart tool/configure_firebase.dart dev (firebase_core)
then dart tool/enable_firebase_sign_in.dart (firebase_auth)
If the app signs in with Google, it also gives Google the app's name and the Firebase CLI account's email for its consent screen, registers the SHA-1 and SHA-256 of this machine's Android debug key (creating the key if there is none, as the first debug build would), downloads the app's Firebase files again and writes the iOS URL scheme into ios/Runner/Info.plist.
A run asks before it runs this step, and leaves it for later when it cannot ask.

"By hand" lists the steps by hand of an app that has any, after "After generation": the link to their guide, then each step with its id, its title and, in parentheses, who lists it. These are the lines of an app with apple_sign_in:

By hand
https://doc.saymyframe.com/setup?steps=app_entry.ios_app_id,firebase_auth.apple_provider,firebase_auth.apple_revocation
app_entry.ios_app_id: In Xcode, sign the iOS app with your paid Apple Developer team (apple_sign_in)
firebase_auth.apple_provider: Turn on Sign in with Apple in Firebase Authentication (firebase_auth)
firebase_auth.apple_revocation: Give Firebase your Sign in with Apple key, so that deleting an account works (firebase_auth)

"Machine" gets a line for each check a module needs, with instructions for what is missing. These are the lines of the same app on a machine without the Firebase CLI:

Machine
✓ Flutter SDK
✗ Firebase CLI (for firebase_core): missing
Install it with "npm install -g firebase-tools", or see https://firebase.google.com/docs/cli.
An interactive run offers to set it up.
✗ Firebase login (for firebase_core): missing
Install the Firebase CLI, then log in with "firebase login", or on a remote machine, such as over SSH, with "firebase login --no-localhost".
✓ FlutterFire CLI 1.4.1 or a later 1.x (for firebase_core)
✓ Xcode project tools of flutterfire (for firebase_core)
✓ Setup of the Xcode project on a Mac (for firebase_core)
✗ Firebase CLI 15.34.0 or later (for firebase_auth): missing
Install it with "npm install -g firebase-tools", or see https://firebase.google.com/docs/cli.

The machine and external setup​

Before it generates anything, smf create checks the machine: the Flutter SDK for every app, and whatever the modules need, such as the Firebase CLI for the Firebase modules. The Flutter SDK must satisfy the SDK constraints of the app, which means Flutter 3.44 or newer, or the run stops, as troubleshooting shows.

SMF checks the machine for a module after the modules it depends on, wherever you named them, so a check can count on a tool that an earlier one installed.

When something is missing, the checks of the built-in modules only warn and print instructions. In a terminal, SMF offers to set up what it can. Each question says what the check found, such as a tool that is missing or an older version of it, and how to set it up by hand, then asks whether to set it up now. With --skip-external-setup, it never installs anything, logs in or sets up an external service, and prints the instructions instead.

Some modules run a command in the new app, such as dart tool/configure_firebase.dart dev. A module marks such a step as skippable when the app is complete without it, as firebase_core does. A skippable step may not run, because the run has no terminal, skips external setup or lacks a tool the step needs. It may also fail, or you may leave it for later. In each case smf create still creates the app and prints the command to run in it later, with the reason.

Nothing went wrong when a step is left for later, so the run does not warn of it. That is a step that you chose to run later, a step that a run without a terminal or with --skip-external-setup holds back, and a step that continues one of these. The run lists such steps after the line that the app is created, in the order in which they run. In an app with steps by hand, the line about them follows the list, and the lines on running the app come last. When the command needs a tool that the machine lacks, the reason names that tool too, as on a machine without the Firebase CLI:

Created my_app in /Users/you/projects/my_app.
One step is left for later. Run it in the app:
Configuring Firebase with flutterfire (the run skips external setup, and Firebase CLI and Firebase login are missing):
dart tool/configure_firebase.dart dev
Then run the app:
cd /Users/you/projects/my_app
flutter run

The run warns of a step when its command fails, or when it would have run the step and a tool that the step needs is missing, as after you decline to install the Firebase CLI in a terminal. The warning comes before the line that the app is created:

[WARN] Configuring Firebase with flutterfire is not done, because Firebase CLI and Firebase login are missing. Run it in the app: dart tool/configure_firebase.dart dev

A step may come with a notice: something to know before it runs that its description does not tell, such as a side effect of the tool that it runs. A run in a terminal asks before it runs such a step, with the notice in the question, and a run that cannot ask leaves the step for later. The notice follows the command of a step that is left for later, and --explain shows it under the command.

The step of firebase_auth that enables the sign-in methods in the Firebase project is such a step, and it continues the configuration of Firebase. When that command is left for later, the step waits with it. The list then has both, and the step refers to the command that it continues by its number:

2 steps are left for later. Run them in the app, in this order:
1. Configuring Firebase with flutterfire (the run skips external setup, and Firebase CLI and Firebase login are missing):
dart tool/configure_firebase.dart dev
2. Enabling the sign-in methods of the app in its Firebase project (it runs after step 1):
dart tool/enable_firebase_sign_in.dart
If the app signs in with Google, it also gives Google the app's name and the Firebase CLI account's email for its consent screen, registers the SHA-1 and SHA-256 of this machine's Android debug key (creating the key if there is none, as the first debug build would), downloads the app's Firebase files again and writes the iOS URL scheme into ios/Runner/Info.plist.

A step that the app cannot do without either runs or stops the run.

Steps by hand​

Some of the setup of an app is out of reach of any command, such as uploading the APNs key of push messages to Firebase, or signing the iOS app with a paid Apple Developer team. The modules of the app list such steps, and the run ends with them, after the steps that are left for later. This is the end of a run for an app with firebase_analytics that skips external setup:

Created my_app in /Users/you/projects/my_app.
One step is left for later. Run it in the app:
Configuring Firebase with flutterfire (the run skips external setup, and Firebase CLI and Firebase login are missing):
dart tool/configure_firebase.dart dev
Finish the setup by hand in one step:
https://doc.saymyframe.com/setup?steps=firebase_analytics.google_analytics
Then run the app:
cd /Users/you/projects/my_app
flutter run

A service knows each flavor of the app as an app of its own, so such a step repeats for each flavor that needs it.

The link opens Finish the setup with the steps of the app in the order to do them, each with a link to its guide, click by click. Do the steps once the commands that are left for later have run. The run asks nothing about these steps, so a run without a terminal, with --no-input or with --skip-external-setup prints the same lines. The README of the app lists the same steps, with the same link, in its first section, "Finish the setup". Without the link of a run, the page lists every step of the built-in modules.

Exit codes​

CodeMeaning
0The app was created, or smf printed help, the version or the report of --explain.
1Generation failed, and the messages say why: an error of a module or a warning about the app with --strict, an error of a module that every app needs, a machine without what the app needs, such as a Flutter SDK that is missing or too old, a failed command, or --on-conflict cancel. If the app was generated already, it stays in a temporary directory that the message names.
64The command line was wrong or incomplete: an unknown option or module, a missing app name, a value of --allow-warning that is not a code, or a question that a run without a terminal cannot ask.
70An unexpected error, which is a bug of smf or of a module. Run again with --verbose for the full log, and please report it.
130The run was cancelled with Ctrl-C, by the end of the input, with Cancel in the question about an existing directory, or, on macOS and Linux, with SIGTERM. Nothing of the app is left behind.

In scripts and CI​

A run for a script names everything and asks nothing:

smf create my_app \
--org com.example \
-m home,bottom_tabs,get_it,bloc \
--no-input \
--skip-external-setup \
--strict \
--on-conflict replace

SMF's own CI generates its apps the same way, without a terminal and with --strict. It checks each app with flutter analyze and runs tests in it, such as the start-up of Firebase, and it starts apps on an Android emulator and an iOS simulator. Every night it does so on every stable release of Flutter that the apps allow.