# Sockets and contributions

A module does not edit files. It returns contributions, plain data that says what it adds, and the pipeline puts them together. Shared files, such as `bootstrap.dart`, have sockets: named places where modules put code without knowing who else does.

## Contributions

| Contribution | What it adds |
| --- | --- |
| `BrickContribution` | Template files, as a [mason](https://pub.dev/packages/mason) bundle, with the variables that the templates read. Every generated file has exactly one owner. |
| `SocketContribution` | Code or a value for a socket. |
| `RoleData` | Data for a role in the role's own language, such as the routes of a feature for the router. |
| `PubspecContribution` | A dependency, an SDK constraint, or a setting of the `flutter:` section of `pubspec.yaml`. |
| `CodegenRequest` | A request to run `build_runner` in the new app. |
| `Preflight` | Checks of the machine before generation, which can offer to set up what is missing. |
| `PostGenStep` | A command to run in the new app, such as `dart tool/configure_firebase.dart dev`. |
| `ManualStep` | A step of the setup that only the developer of the app can do, such as uploading a key to the Firebase console. The run ends with a link to its guide; see [steps by hand](https://doc.saymyframe.com/extending/infrastructure-module.md#steps-by-hand). |

Every contribution can have a `when`, a set of roles that must all be present for the contribution to apply. A module lists there the roles it only uses, so code that refers to them is generated only in apps that have them. `firebase_analytics` gives the router its listener of the screen with `when: {routerRole}`.

A brick and data of a role can also depend on what a role chose, with `whenChosen`: conditions that a role publishes as constants, such as `RouterRole.noStartRoute`, which holds in an app that no route starts. `flutter_core` contributes its fallback start screen, the test of the screen and its two texts under that condition, so an app that a route starts has none of them. A role chooses after the machine is checked, so such a condition decides only what the pipeline renders:

- Validation and `--explain` take a contribution with conditions as one that applies, and report its problems in every app that may have it.
- A role whose data depends on what another role chose makes its choice after that role, and gets only the data whose conditions hold.
- Once every role has chosen, the contributions whose conditions do not hold leave the app. The render hooks, the templates and the checks of the rendered app do not see them.

Only bricks and data of a role take such conditions, because the pipeline needs the other kinds before any role chooses. A brick with conditions holds no tag of a socket, since an app without the brick would lose what the socket got. See [Choices](https://doc.saymyframe.com/extending/new-role.md#choices-and-options).

## Sockets

A socket belongs to a role, to a module or to the pipeline. Its template marks it with a tag in three braces that joins `smf_`, the owner and the name of the socket, such as `{{{smf_app_entry__bootstrap_platform}}}`. The pipeline replaces the tag with the rendered contributions. Modules refer to sockets only through typed constants, such as `AppEntryRole.bootstrapPlatform`, so a misspelled socket does not compile.

This is `lib/bootstrap.dart` as `flutter_core` writes it, with the tags of the start-up phases:

```dart
{{{smf_app_entry__top_level}}}

Future<void> bootstrap() async {
{{{smf_app_entry__bootstrap_early}}}
{{{smf_app_entry__bootstrap_platform}}}
{{{smf_app_entry__bootstrap_di}}}
{{{smf_app_entry__bootstrap_late}}}
}
```

```mermaid
flowchart LR
    fc["firebase_core"] -- "await Firebase.initializeApp(...)" --> platform
    crash(["Crash reporting role"]) -- "installCrashReporting()" --> platform
    di(["DI role"]) -- "await registerDependencies();" --> diPhase
    subgraph boot ["bootstrap() of flutter_core"]
        early["early"]
        platform["platform"]
        diPhase["DI"]
        late["late"]
    end
```

The kind of a socket decides what it takes and how its contributions are rendered:

| Kind | Takes | Rendered as | Example |
| --- | --- | --- | --- |
| `CodeSocket` | Fragments of code, with the imports they need | One after another | The start-up phases |
| `ArgsSocket` | Named arguments of a call | `name: value,` lines. A list argument unites the items of its contributors, or takes the items of one contributor only. Two values of a single argument conflict. | `theme` and `supportedLocales` of `MaterialApp` |
| `WrapperSocket` | A widget around a piece of code, as an opening and a closing part | The first contribution outermost | `ProviderScope(child: …)` around the root widget |
| `FactoryListSocket` | Functions that the socket's owner calls | List items | Navigator observers and listeners of the screen of the router, restorers of the preferences |
| `KeyedSocket` | Entries with a key | Merged by key with a policy | Android permissions, `Info.plist` keys, Gradle plugins, README sections, sections of the guide for coding agents |
| `ValueSocket` | One value, rendered at one tag, or at several places with a tag each (`ValueSocket.places`) | Merged with a policy | The minimum iOS version, the highest any module needs, and the version of core library desugaring, which renders a line in two places of the Gradle file |

A fragment of code never imports anything itself. It lists its imports, such as `ImportRef('package:firebase_core/firebase_core.dart')`, and the pipeline adds them to the file that holds the socket's tag, merged and sorted. `ImportRef.app('firebase_options_of_flavor.dart')` imports a file of the app without knowing the app's package name.

### The sockets of the app entry

The app entry role is open to every module, so any module can use these sockets:

| Socket | Where | What goes in |
| --- | --- | --- |
| `bootstrapEarly`, `bootstrapPlatform`, `bootstrapDi`, `bootstrapLate` | `bootstrap()` | Start-up code, by phase |
| `topLevel` | `lib/bootstrap.dart` | Top-level declarations, such as a handler of background messages |
| `rootWrappers` | `main()` | Widgets around the root widget |
| `appArgs` | the root `MaterialApp` | `theme`, `darkTheme`, `themeMode` and `locale`, which take one value, `localizationsDelegates`, which takes the list items of every contributor, and `supportedLocales`, which takes the list items of one contributor |
| `appBuilder` | the `builder` of the root `MaterialApp` | Widgets around the content of every route |
| `iosDeploymentTarget` | `project.pbxproj` | The minimum iOS version, where the highest wins |
| `androidManifestPermissions` | `AndroidManifest.xml` | `<uses-permission>` by name |
| `androidManifestApplicationMeta` | `AndroidManifest.xml` | `<meta-data>` of the application |
| `androidManifestApplicationComponents` | `AndroidManifest.xml` | `<receiver>`, `<service>` and `<provider>` elements inside `<application>`, after its meta-data, as XML |
| `mainActivityIntentFilters` | `AndroidManifest.xml` | `<intent-filter>`s of the main activity, such as deep links |
| `infoPlist` | `Info.plist` | Keys with a string, a boolean, an integer or an array of strings, where arrays are united |
| `iosEntitlements` | `ios/Runner/Runner.entitlements` | Entitlements by key, such as `aps-environment`, merged as the keys of `Info.plist` |
| `iosAppDelegateLaunch` | `AppDelegate.swift` | Swift statements that run in `application(_:didFinishLaunchingWithOptions:)`, before the call of `super.application` that hands the launch to Flutter |
| `gradleSettingsPlugins`, `gradleAppPlugins`, `gradleAppDependencies` | Gradle files | Plugins with their versions, the plugins the app applies, and dependencies, where the highest version wins |
| `androidCoreLibraryDesugaring` | the Gradle file of the app | The version of `desugar_jdk_libs`, where the highest wins. It turns core library desugaring on in `compileOptions` and adds the library to `dependencies`, since the build fails with either line alone. |
| `readmeSections` | `README.md` | Sections by heading, such as the setup of Firebase |
| `agentSections` | `AGENTS.md` | Sections of the guide for coding agents by heading, each with a note |

The provider of the app entry creates the root `MaterialApp` in the `build` of a widget, below the root wrappers, and evaluates the arguments of `appArgs` there. So the expression of an argument may read `context`: it can take its value from an inherited widget that a module put among the `rootWrappers`, and the root rebuilds when that widget notifies. The theme role gives the root its `themeMode` this way, and the localization role its `locale`. That context is above the `MaterialApp`, so it has neither the theme nor the localizations of the app.

The wrappers of `appBuilder` are below the `MaterialApp`, so a widget among them finds the theme and the localizations of the app above it. The app entry role puts one there itself, in every app: `StatusBarIcons` of `lib/core/app/status_bar_icons.dart`, which tells the system which icons of the status bar suit the theme.

A few native sockets ask more of what goes in:

- A contribution to `androidManifestApplicationComponents` indents its elements by eight spaces, and one to `iosAppDelegateLaunch` its lines by four, as the bodies around their tags are.
- The manifest declares a component of each kind once, `<activity-alias>` included, under one `android:name`. The contract harness checks the names as Android reads them, so a name that starts with a dot, or has none, is relative to the `namespace` of the Gradle file of the app. Android fails to build an app with two declarations of a component that differ, and keeps both of two equal ones.
- The statements of `iosAppDelegateLaunch` run before Flutter registers the plugins of the app, which the scene of the app does once it connects. So they have no registry of plugins and no channel. `local_notifications` sets the delegate of the notification center there.

The router role adds `observers` and `screenListeners`, and two families of sockets for the annotations of screens and of their parameters, which a router that needs annotations fills. The preferences role adds `restorers`, for the functions that restore what the modules keep in the preferences when the app starts.

## Who may contribute where

- A socket of a role takes contributions from the modules that provide, require or use the role, and from the templates of the role and of the roles that require or use it. The sockets of the app entry are open to every module.
- A socket of a module takes contributions from the modules that depend on it directly.
- The sockets of the pipeline, which are the sections of `pubspec.yaml`, take no contributions. Modules contribute `PubspecContribution`s instead, and the pipeline merges them.

A module may contribute data only to its own roles, and only a role's providers contribute its implementations. The pipeline checks all of it before it renders anything.

## Order

When several modules put code into one socket, or several steps run after generation, they come in an order that the pipeline derives from the modules alone:

1. A module comes after the modules it depends on.
2. A module that requires a role, or names it in `when`, comes after the role's providers and its template.
3. A role's template comes after the role's providers, and after the providers and templates of the roles that its role requires or that its contributions name in `when`.

A contributor comes after every contributor it reaches through these edges, even through modules that add nothing to the socket. Otherwise contributors are ordered by id. A cycle is an error. `smf create --explain` prints the order of every socket with more than one contributor, and the edges that decide it. A step that continues another step runs right after that step instead of in its own place; see [Continuing a step](https://doc.saymyframe.com/extending/infrastructure-module.md#continuing-a-step).

That is why `installCrashReporting()` of the crash reporting role runs after `Firebase.initializeApp` of `firebase_core`: the role's template comes after its provider, `firebase_crashlytics`, which depends on `firebase_core`.

A keyed socket can put its entries in an order of its own instead. The sections of the guide for coding agents do: the section of the app entry comes first, and the others follow in the order of their headings. The pipeline takes the notes of such a socket by the ids of their contributors alone, so a `when` of a note adds no edge, and no note can make an app impossible to generate. `--explain` does not list such a socket, which has no order of contributors to explain.

## Merging

Keyed and value sockets merge the contributions of several modules with a policy of the socket:

| Policy | How values merge | Used for |
| --- | --- | --- |
| conflict | Equal values agree, and different values are an error that names both modules. | Android permissions, README sections |
| max | The highest version wins. | The minimum iOS version, Gradle plugins, core library desugaring |
| `Info.plist` | Arrays of strings are united, and every other value must agree. | `Info.plist` keys, entitlements |
| notes | The notes under one heading are united, each once: those of roles first, then those of modules. | Sections of the guide for coding agents |

The pipeline merges `pubspec.yaml` itself. It intersects the constraints of a package that several modules add, and an empty intersection is an error. A package that is both a dependency and a dev dependency becomes a dependency.
