Skip to content

A Flutter app in one command

Pick what the app needs, such as a router, tabs at the bottom, dependency injection, a state manager and Firebase. smf create generates a Flutter project in which these parts already work together.

The project does not depend on SMF at run time, so its code is yours from the first commit.

dart pub global activate smf_flutter_cli
smf create --explain
$ smf create my_app --org com.example -m home,bottom_tabs,get_it,event_bus --explainApp 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

--explain prints the plan and stops before it writes anything. This is the start of its report, from the documentation.

SMF generates apps for Flutter 3.44 or newer and Dart 3.12 or newer. It is tested on macOS, Linux and Windows.

How it works

The elements of an app

SMF builds an app from roles, such as the router or dependency injection, and modules fill them. Pick a module for each role the app needs and leave out the rest. A module that needs a role works with every module that provides it, so you can swap one for another and the app still compiles.

  1. Choose the modules

    Pick the features and a module for each role the app needs, in the questions of smf create or with -m.

    -m home,bottom_tabs,get_it,bloc

  2. Run smf create

    SMF adds the modules that your choice needs, generates the project, and applies dart fix and dart format.

    smf create my_app -m home,bottom_tabs,get_it,bloc

  3. Run the app

    You get a regular Flutter project in which the parts already work together. Start it with flutter run.

    cd my_app && flutter run

Router

An app has none or one.

The routes of the screens that features declare, and a typed way to reach them. A screen navigates with context.nav.home.home().go() rather than through the router, so its code works with any router. With a layout, the router also builds the main navigation.

Your code uses

  • context.nav
  • AppRouter

Modules that provide it

  • go_router

    Routes and a typed navigation facade, with go_router.

How to choose

Choose it with -m go_router or in the questions of smf create. A feature or a layout needs a router, so SMF adds go_router by itself while it is the only router.

Navigation

Features, such as the start screen home, and infrastructure, such as firebase_core, provide no role. Every module has a page in the documentation.All modules

The app

Every file has one author

Some files come from a module and others from a role, whichever module provides it. That split lets modules replace each other.

Routes and typed navigation
With a router, every screen of a feature has a route. Screens navigate with context.nav rather than through go_router, so their code works with any router.
Navigation
Tabs that keep their stacks
With bottom_tabs, the routes that features mark as destinations become the tabs of a Material 3 NavigationBar, and each tab keeps its own stack.
bottom_tabs
Services behind the state
With get_it, the app registers the services of the modules before its first frame. Screens reach them only through their state, a Cubit or a Riverpod provider.
Services and state
Start-up in the right order
bootstrap() runs the start-up code of the modules in phases, so Firebase and crash reporting are ready before the services of the app.
Start-up
Firebase and its setup
SMF checks for the Firebase CLI, a login and the FlutterFire CLI, and offers to set up what it can. After generation it asks whether to run flutterfire configure.
Firebase
Code you own
SMF applies dart fix and dart format to the new app. The app has no SMF package in its dependencies, and nothing in it has to be generated again.
It is your code now

my_app · home, bottom_tabs, get_it, event_bus

The files of my_app and who wrote each
FileWritten by
my_app/
README.mdflutter_core
analysis_options.yamlflutter_core
pubspec.yamlflutter_core
android/flutter_core
ios/flutter_core
lib/
main.dartflutter_core
bootstrap.dartflutter_core
app.dartflutter_core
core/
app/fallback_start_screen.dartflutter_core
di/service_locator.dartDI role
di/dependencies.dartget_it
events/communication_service.dartevents role
events/event_bus_communication_service.dartevent_bus
layout/destination.dartlayout role
layout/app_shell.dartbottom_tabs
router/app_router.dartrouter role
router/navigation.dartrouter role
router/app_router_factory.dartgo_router
features/home/home_screen.darthome
test/flutter_core
Navigating from a screen
context.nav.home.home().go();

For teams

For teams and module authors

SMF rests on one principle: modules do not know each other. Roles, sockets and the checks that smf create runs keep it that way, so a team can swap the provider of a role or add modules of its own.

Modules depend on roles
A module names only the modules it depends on, as the Firebase modules name firebase_core. For everything else it names a role, such as a router or a DI container, and whichever module the user picked provides it. A screen needs a router, and analytics follows the screens of whichever router the app has.
The module model
Sockets in shared files
A module does not edit files. Shared files such as bootstrap.dart have sockets, named places where modules put code, and smf create orders and merges what the modules contribute.
Sockets and contributions
Modules of your own
A module is an ordinary Dart package built on smf_contracts. A command of your own, built on smf_flutter_cli, offers your modules next to the built-in ones, with every option of smf create.
Extending SMF
A contract harness
The harness in smf_pipeline renders every app that matters for a module in memory and checks it against the rules of the module model. The built-in modules test themselves with it.
Test a module
Combinations checked in CI
CI generates apps from the modules in many combinations, each module with each provider of the roles it requires and every module together, and runs flutter analyze on each.
Module independence
Ready for scripts
With --no-input a run asks nothing, and with --strict it stops rather than leave a module out. --explain prints the plan without writing a file, and exit codes tell a script what happened.
Scripts and CI
The module home
final class HomeModule extends SmfModule {
  const HomeModule();

  static const id = ModuleId('home');

  @override
  ModuleDescriptor get descriptor => const ModuleDescriptor(
        id: id,
        description: 'Start screen with the name of the app',
        kind: ModuleKinds.feature,
      );

  @override
  List<Contribution> contribute(ModuleContext context) => [
        BrickContribution(homeBundle),
        routerRole.data(const RoutesData([/* the route of the screen */])),
      ];
}
The contract test of a counter module, from the docs
final harness = ContractHarness(
  ModuleRegistry(const [
    FlutterCoreModule(),
    GoRouterModule(),
    GetItModule(),
    BlocModule(),
    RiverpodModule(),
    CounterModule(),
  ]),
);
final results = await harness.checkAll();
for (final result in results) {
  expect(result.errors, isEmpty, reason: '${result.contractCase}');
}

Free and open source

SMF is developed in the open under the Apache License 2.0. Bug reports and ideas go to GitHub Issues, and questions are welcome on Discord.

Release notes by email

Get an email when SMF or its modules have a new release.

We use Resend. You can unsubscribe at any time.

Contact

Ask questions on Discord, and report bugs or ideas on GitHub. For anything else, write to us here.

DiscordGitHub Issues

Write to us