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
| Option | What 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. |
--strict | Stop instead of leaving out a module that cannot work; see lenient and strict. |
--explain | Print what would be generated and whether the machine is ready, then stop without changing anything; see below. |
--skip-external-setup | Never install tools, log in or configure external services; print what to run instead. |
--[no-]input | Whether 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. |
--verbose | Report more of what happens. It can come before or after create. |
-h, --help | Print 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 namespace | com.example.my_app |
| iOS bundle id | com.example.my-app (bundle ids take hyphens, not underscores) |
Questions
smf create asks questions only when it runs in a terminal, without --no-input and without --explain. It asks for:
- the app name, unless it is given;
- what to do with an existing directory, unless
--on-conflictsays; - the organization, unless
--orggives it; - the modules, unless
-mgives them: first the modules without a role by kind, such as the features, then a provider for each role (see Create your first app); - 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; - 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);
- what the roles of the app leave open: the start screen, when several screens can start the app and
--startdoes not say, and who may use the app without an account, in an app with a module that provides sign-in, unless--auth-modesays; - whether to run a step of a module after generation now, such as
flutterfire configure, 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.
An existing directory
When the app's directory exists and is not empty, --on-conflict decides:
| Value | What happens |
|---|---|
prompt | A 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. |
replace | SMF 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. |
copy | The app goes into a new directory next to it: my_app copy, or my_app copy 2 if that exists too. |
cancel | Nothing 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.
With --strict, such a problem stops the run with exit code 1 before anything is generated. Use --strict in scripts and CI, where an app without a module you asked for is a failure.
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.
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 flutterfire configure 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 pub global run flutterfire_cli:flutterfire configure --platforms=android,ios --overwrite-firebase-options --ios-bundle-id=com.example.my-app --android-package-name=com.example.my_app (firebase_core)
then dart tool/enable_firebase_sign_in.dart (firebase_auth)
The Firebase CLI adds a web app named "Default Web App" to a project that has no web app, because it enables the methods through one (firebase/firebase-tools#11250). The page https://console.firebase.google.com/project/_/authentication/providers of the Firebase console enables them without it.
A run asks before it runs this step, and leaves it for later when it cannot ask.
"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.6.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 flutterfire configure. 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, and the lines on running the app follow the list. 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 pub global run flutterfire_cli:flutterfire configure --platforms=android,ios --overwrite-firebase-options --ios-bundle-id=com.example.my-app --android-package-name=com.example.my_app
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 pub global run flutterfire_cli:flutterfire configure --platforms=android,ios --overwrite-firebase-options --ios-bundle-id=com.example.my-app --android-package-name=com.example.my_app
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 flutterfire configure. 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 pub global run flutterfire_cli:flutterfire configure --platforms=android,ios --overwrite-firebase-options --ios-bundle-id=com.example.my-app --android-package-name=com.example.my_app
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
The Firebase CLI adds a web app named "Default Web App" to a project that has no web app, because it enables the methods through one (firebase/firebase-tools#11250). The page https://console.firebase.google.com/project/_/authentication/providers of the Firebase console enables them without it.
A step that the app cannot do without either runs or stops the run.
Exit codes
| Code | Meaning |
|---|---|
0 | The app was created, or smf printed help, the version or the report of --explain. |
1 | Generation failed, and the messages say why: an error of a module with --strict, or 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. |
64 | The command line was wrong or incomplete: an unknown option or module, a missing app name, or a question that a run without a terminal cannot ask. |
70 | An unexpected error, which is a bug of smf or of a module. Run again with --verbose for the full log, and please report it. |
130 | The 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.