# The module model

SMF builds an app from modules. To get something done, like routing or dependency injection, a module names a role ("a router", "a DI container"), and whichever module the user picked provides that role. It names another module only when it builds on it, as the Firebase modules build on `firebase_core`, or when it has a variant for one provider of a role. The sections below describe the pieces, and [module independence](https://doc.saymyframe.com/concepts/independence.md) explains why the split matters.

```mermaid
flowchart LR
    home["home<br/>feature"] -- requires --> router(["Router role"])
    tabs["bottom_tabs<br/>layout"] -- provides --> layout(["Layout role"])
    layout -- requires --> router
    gorouter["go_router"] -- provides --> router
    analytics["firebase_analytics"] -- provides --> analyticsRole(["Analytics role"])
    analyticsRole -. uses .-> router
    analytics -- depends on --> core["firebase_core"]
```

Rectangles are modules, and rounded boxes are roles. `home` and `bottom_tabs` need a router, and `go_router` provides it. The analytics role works with a router when the app has one. `firebase_analytics` depends on `firebase_core` directly, because it builds on Firebase itself rather than on a role.

## Modules

A module is a Dart class that extends `SmfModule`. It describes itself with a descriptor and returns its contributions to an app, such as files, code for sockets, data for roles and dependencies:

```dart
final class HomeModule extends SmfModule {
  const HomeModule();

  static const id = ModuleId('home');

  @override
  ModuleDescriptor get descriptor => const ModuleDescriptor(
        id: id,
        description: 'Start screen with a welcome and the next steps',
        kind: ModuleKinds.feature,
      );

  @override
  List<Contribution> contribute(ModuleContext context) => [
        BrickContribution(homeBundle),
        routerRole.data(const RoutesData([/* the route of the screen */])),
      ];
}
```

`contribute` gets a `ModuleContext` with the name of the app, its organization, its platforms and its platform identifiers. It says nothing about the other modules of the app. Code that depends on other roles goes into contributions that apply only when those roles are present.

The descriptor says:

| Field | What it means |
| --- | --- |
| `id` | The id of the module, lower snake_case, as in `-m home`. |
| `description` | What the module adds, as the questions of `smf create` show it. |
| `kind` | The kind of the module, which decides the rules it follows. |
| `providers` | The roles the module provides, one provider object per role. |
| `requires` | Roles the module needs. The app gets a provider of each. |
| `uses` | Roles the module works with when they are present, and works without. |
| `dependsOn` | Modules the module builds on directly, such as `firebase_core` for the Firebase modules. |
| `variants` | Alternative contributions for the providers of one role, such as a feature with a BLoC and a Riverpod variant. |
| `sockets`, `socketFamilies` | Sockets of the module itself, for the modules that depend on it. |

## Kinds

The kind of a module states, as data, the rules its modules follow. The pipeline applies them without knowing any kind.

| Kind | Modules | Rules |
| --- | --- | --- |
| scaffold | `flutter_core` | Provides the app entry role: `main()`, start-up, native projects, `pubspec.yaml`. |
| infrastructure | `go_router`, `get_it`, `bloc`, `riverpod`, `material_theme`, `gen_l10n`, `event_bus`, `shared_preferences`, the Firebase modules | Sets up a service or a library. Declares no routes, generates nothing in `lib/features/`, has no variants, and never resolves services itself: it gets them through the factories of its DI registrations. |
| feature | `home`, `onboarding`, `settings`, `sign_in` | A module with screens. Requires the router role, declares at least one route, and keeps its files in `lib/features/<id>/`. Its optional composition file, `lib/features/<id>/<id>_composition.dart`, may resolve services; a feature that does requires the DI role. |
| layout | `bottom_tabs` | Provides the layout role, the main navigation. Keeps its files in `lib/core/layout/` and declares no routes. |

A kind can also list rules of roles, which only their roles check. The composition file of a feature is a rule of the DI role, which lets a module resolve services only in the composition file that its kind lists. A kind that another package defines can list one of its own.

A scaffold provides the app entry role, and a layout the layout role. Beyond that, the kind of a module does not decide which roles it provides: `settings` is a feature, with a route and a screen like any other, and it provides the settings screen role.

In the questions of `smf create`, the modules that provide no role are grouped by kind: Features, Infrastructure.

## Roles

A role is a replaceable part of an app, described by what it offers to the rest of the app. It has:

| Part | Meaning |
| --- | --- |
| cardinality | Exactly one provider in every app, at most one, or any number. |
| requires, uses | Other roles, whose data and presence its hooks can see. |
| sockets | Named places in its files where other modules put code. |
| interface | The files its own template generates, and the symbols every provider must generate, such as `createAppRouter()` for the router. |
| data type | The type in which modules describe what they need from it, such as the routes of a feature. |
| options | Options of the command line, such as `--start` of the router. An option whose every value makes another app, such as `--auth-mode` of the auth role, is a mode option: its first value is the default, and every value gets apps in the tests. |
| template, rules | Hooks that run during generation, and checks of the modules and of the generated code. |

A role is defined once, as a constant, in `smf_contracts` or in any other package. These are the built-in roles:

| Role | Id | Providers in an app | Requires | Uses | Data from modules |
| --- | --- | --- | --- | --- | --- |
| App entry | `app_entry` | exactly one | | Router | none |
| Router | `router` | at most one | | Layout | `RoutesData`: routes, [guards](https://doc.saymyframe.com/concepts/guards.md) and the [conditions](https://doc.saymyframe.com/concepts/conditions.md) that routes ask for |
| Layout | `layout` | at most one | Router | Localization | none |
| Settings screen | `settings_screen` | at most one | Router | | `SettingsData`: the entries of the screen, and its route |
| Onboarding screen | `onboarding_screen` | at most one | Router | Localization | `OnboardingData`: the [steps](https://doc.saymyframe.com/extending/onboarding-steps.md) of the onboarding, and its route |
| Localization | `localization` | at most one | Preferences | Settings screen | `TextsData`: [texts](https://doc.saymyframe.com/concepts/localization.md) |
| State management | `state_management` | at most one | | | none |
| Dependency injection | `di` | at most one | | | `DiRegistration`: services |
| Events | `events` | at most one | | Dependency injection | `RoleImplementation` |
| Preferences | `preferences` | at most one | | Dependency injection | `RoleImplementation` |
| Theme | `theme` | at most one | Preferences | Settings screen, Localization | none |
| Analytics | `analytics` | any number | | Dependency injection, Router | `RoleImplementation` |
| Crash reporting | `crash_reporting` | any number | | Dependency injection | `RoleImplementation` |
| Authentication | `auth` | at most one | | Router, Localization | `AuthData`: the `AuthImplementation` of the provider, and the `SignInMethod`s of the [ways to sign in](https://doc.saymyframe.com/guides/sign-in.md#ways-to-sign-in) |
| Notifications | `notifications` | at most one | Preferences | Settings screen, Onboarding screen, Localization | `RoleImplementation` |
| Push messages | `push` | at most one | Notifications | | `RoleImplementation` |
| REST API | `rest` | at most one | Values in .env | Authentication | `RoleImplementation` |
| CI | `ci` | at most one | | Values in .env | none |
| App icon | `app_icon` | at most one | | | none |
| Splash screen | `splash` | at most one | | | none |

The app entry is open to every module, so any module may add start-up code or native settings without declaring the role. It uses the router role, so that its provider may contribute the fallback start screen only to an app that no route starts. Its provider builds the root of the app, inside the wrappers that modules add, as a `MaterialApp`, or as a `MaterialApp.router` when the app has a router. So the screens of every module have the Material ancestors they rely on, such as the theme and `MaterialLocalizations`.

### Role templates

Much of a role's code does not depend on its provider, so the role generates it itself, through its template. The router role generates the navigation facade from the routes of all features, the DI role generates the `ServiceLocator` interface, and the analytics role generates the `AnalyticsService` interface with a service that forwards every call to all providers. The auth role generates `appSession`, the session of an app with sign-in. The notifications role generates `appNotifications`, with an entry of the settings screen and a step of the onboarding that ask for the permission, and the push role generates `appPush`. The app entry role generates the guide for coding agents of the app. The pipeline treats the template like a module named `role:<id>`, which owns the files it generates.

A template contributes like a module. It can put code into the sockets of the roles that its role requires or uses, and give those roles data. The template of the theme role keeps the theme mode that the user selects: it gives the preferences role a function that restores the mode, the settings screen role a row that selects it, and the localization role the texts of that row. So the mode works the same with every module that provides the theme. [How settings work](https://doc.saymyframe.com/concepts/settings.md) follows a setting through these roles.

### Providers

A module provides a role with a provider object in its descriptor. A provider may check the role's data against what it supports, as `bottom_tabs` does with its limit of five destinations. It may also render the data into its own files: `go_router` renders the routes into a `GoRouter`, and `get_it` renders the registrations into get_it calls. A module whose files and dependencies alone implement the role, such as `bloc`, uses `RoleProvider.plain`.

A provider requires and uses what its role requires and uses, so `bottom_tabs` requires a router because the layout role does.

## How modules relate

| Relation | Direction | What it gives |
| --- | --- | --- |
| provides | module → role | The module implements the role. |
| requires | module → role | The app gets a provider of the role, and the module may use its sockets and symbols. |
| uses | module → role | The module works with the role when it is present. Its code that refers to the role's symbols goes into contributions that name the role in `when`. In its templates, that code is inside `{{#has_<role>}}` or in a variable whose code depends on the role. Its data for the role and its code for the role's sockets apply only when the role is present anyway. |
| depends on | module → module | The module builds on another module directly, and that module comes into the app with it. It may put code into that module's sockets. The id comes from the other module's package, so the dependency is a pub dependency too. |
| variants | module → providers of one role | The module has different contributions for each provider, keyed by the provider's id. It requires the role. |

Every relation points one way. `firebase_analytics` depends on `firebase_core`, but `firebase_core` knows nothing about it. Whenever a role describes what a module needs, the module requires the role rather than depending on the module that provides it.

Two modules can also meet through something that a role publishes. A role can publish a [condition for routes](https://doc.saymyframe.com/concepts/conditions.md), such as an account. A feature asks for it on a route, another module stands for it with a guard, as `sign_in` does, and each knows only the role.

## Variants

A module whose code depends on the provider of a role declares variants, one per provider, keyed by the provider's id. A feature with state has a variant for `bloc` and one for `riverpod`, as the built-in `sign_in` has, and the pipeline adds the contributions of the variant for the provider that the app has. A variant brings the package of its provider, such as `flutter_bloc`, with the constraint `any`, so the provider keeps control of the version.

A variant only adds contributions and never changes the descriptor. When the app has a provider that the module has no variant for, the module cannot be part of the app.

## Next

- [Sockets and contributions](https://doc.saymyframe.com/concepts/sockets.md) shows how modules put code into the app.
- [The generation pipeline](https://doc.saymyframe.com/concepts/pipeline.md) shows how the pipeline turns modules into an app.
