# Services and state

A generated app can have services for events, analytics and crash reporting, each from a role. The code of the app gets them from a DI container when the app has one, and from plain functions when it does not. Either way, the screens reach them only through their state.

The preferences, which remember the settings of the app, come from a role too. The code of the app gets them when the app starts, or from the container, and [Preferences and settings](https://doc.saymyframe.com/guides/preferences.md) describes how.

Sign-in comes from a role as well, but not as a service of the container. The code of the app reaches it through `appSession`, a top-level variable; see [Sign-in](https://doc.saymyframe.com/guides/sign-in.md). The notifications and the push messages work the same way, through `appNotifications` and `appPush`; see [Notifications and push messages](https://doc.saymyframe.com/guides/notifications.md).

## The services

| Role | Service | File | Module |
| --- | --- | --- | --- |
| Events | `CommunicationService` | `lib/core/events/communication_service.dart` | [`event_bus`](https://doc.saymyframe.com/modules/event-bus.md) |
| Analytics | `AnalyticsService` | `lib/core/analytics/analytics_service.dart` | [`firebase_analytics`](https://doc.saymyframe.com/modules/firebase-analytics.md) |
| Crash reporting | `CrashReporter` | `lib/core/crash_reporting/crash_reporter.dart` | [`firebase_crashlytics`](https://doc.saymyframe.com/modules/firebase-crashlytics.md) |

Each role generates the interface of its service and a function that returns the app's service: `createCommunicationService()`, `createAnalyticsService()` and `createCrashReporter()`. The function returns the same instance every time. An app can have several modules for analytics or crash reporting, and its service then forwards every call to each of them on its own. A module whose service throws or fails keeps no other from the call, and its failure reaches neither the code that called nor the handlers of uncaught errors. In debug mode, the service of the app prints the failure. Each analytics module gets its own copy of the parameters of a call. The app leaves out a service that fails to be created or to start, and works with the others.

The code of the app uses the interface and never the module behind it, so you can replace a module without touching that code.

## With a DI container

With [`get_it`](https://doc.saymyframe.com/modules/get-it.md), each service role registers its service in the container as a lazy singleton, the preferences role too, and other modules register services of their own. `registerDependencies()` does this in `bootstrap()`, before the first frame.

The generated code follows one rule, and the [guide for coding agents](https://doc.saymyframe.com/guides/coding-agents.md) of the app gives it to agents for the code that they add: only the composition file of a feature takes services from the container. The composition file, `lib/features/<id>/<id>_composition.dart`, creates what the screens of the feature need, such as a Cubit, with `resolve<T>()` of `lib/core/di/service_locator.dart`. Everything else gets its services as parameters, and the screens talk only to their state.

With BLoC, the composition file creates the Cubit:

```dart title="lib/features/counter/counter_composition.dart"
import 'package:flutter_bloc/flutter_bloc.dart';

import '../../core/di/service_locator.dart';
import 'counter_store.dart';

/// Creates the cubit of the counter screen with the services it needs.
CounterCubit createCounterCubit() => CounterCubit(resolve<CounterStore>());
```

and the screen provides it with `BlocProvider(create: (_) => createCounterCubit(), ...)` and reads it with `context.read<CounterCubit>()`.

With Riverpod, the composition file bridges the container to a provider:

```dart title="lib/features/counter/counter_composition.dart"
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../core/di/service_locator.dart';
import 'counter_store.dart';

/// The store of the counter, from the service locator of the app.
final counterStoreProvider = Provider<CounterStore>(
  (_) => resolve<CounterStore>(),
);
```

and the notifiers of the feature read it with `ref`. [Write a feature module](https://doc.saymyframe.com/extending/feature-module.md) builds this feature from start to end.

A value known only at run time, such as the id of the item that a screen shows, is a parameter of the function in the composition file, as in `DetailsCubit createDetailsCubit(int id)`. Elsewhere, a factory class creates objects with such values: it takes its services in its constructor, creates the objects in a method such as `call(int id)`, and is registered like any other service, so the code that needs it gets it as a parameter.

In tests, `resetDependencies()` of `lib/core/di/dependencies.dart` disposes of the services that the container created, in the reverse order of their registration, and removes them all, so that `registerDependencies()` can register them again.

## Without a container

Without a DI container, call the function of the service where you need it, such as in the code that creates a Cubit:

```dart
final analytics = createAnalyticsService();
```

It returns the same service every time, so every part of the app shares it. Don't create another one from the module's own implementation, such as `createEventBusCommunicationService()`, because the listeners of one event service do not get the events fired into another.

This is for the code that you write in the app. In the code that modules generate, only the DI container calls these functions, and the contract harness checks that. A feature resolves its services in its composition file instead, which is why a feature with services requires the DI role.

## Events

The events role lets parts of the app that do not know each other exchange events. An event is a class that extends `AppEvent`:

```dart
final class ItemAdded extends AppEvent {
  const ItemAdded(this.id);

  final int id;
}
```

`fire` sends an event, and `on<T>()` is the stream of the events of a type:

```dart
events.fire(const ItemAdded(5));

final subscription = events.on<ItemAdded>().listen((event) {
  // An item was added somewhere in the app.
});
```

An event goes once to every listener of its type that is listening when the event is fired, and the events arrive in the order they were fired. Whether it also goes to the listeners of a type that it extends or implements depends on the module, and it never goes to a listener of any other type. The stream of `on<T>()` has only the events fired after it is listened to, so an event fired between the call of `on<T>()` and `listen` is not in it. A cancelled subscription gets no more events. With `event_bus`, an event also goes to the listeners of the types it extends or implements, so `on<AppEvent>()` gets every event, and listeners get an event after the code that fires it has completed.

## Analytics

`AnalyticsService` logs events and describes the user: `logEvent`, `logSignIn`, `logSignUp`, `setUserId`, `setUserProperty` and `setAnalyticsCollectionEnabled`. In an app with a router, screen views need no code: `firebase_analytics` logs one for each screen the user sees. See [`firebase_analytics`](https://doc.saymyframe.com/modules/firebase-analytics.md#screen-views).

## Crash reporting

`bootstrap()` calls `installCrashReporting()` of the role once the reporting services are ready. It reports as fatal the errors of the main isolate that the app does not handle, through two handlers. `FlutterError.onError` gets the errors that Flutter catches, such as in a build or a layout, and still presents them as Flutter does by default. `PlatformDispatcher.instance.onError` gets every other uncaught error, such as one in a `Future` or a `Timer`, and in debug mode it also leaves the error unhandled, so the engine still prints it.

Report other errors with `recordError`, add context to the next report with `log`, and set the user with `setUserId`. The reporter does not print what it reports, so an error that the app reports is not printed either.

The handlers cover the main isolate. `compute()` and `Isolate.run()` throw the error of their isolate to the code that awaits them, so it reaches the handlers unless that code catches it. For an isolate that the app spawns itself, report its errors in its error listener, such as the `onError` port of `Isolate.spawn`.

## State

The state management role holds the state of screens. [`bloc`](https://doc.saymyframe.com/modules/bloc.md) adds `flutter_bloc`, and [`riverpod`](https://doc.saymyframe.com/modules/riverpod.md) adds `flutter_riverpod` and wraps the app in a `ProviderScope`. A feature that keeps state brings its Cubits or providers in a variant for each state manager, so the same feature works with either.

With either state manager, the widgets of a screen talk only to their state, a Cubit through `context.read` or a provider through `ref`, and the state talks to the services. Widgets never reach into the DI container.

The screens of [`sign_in`](https://doc.saymyframe.com/modules/sign-in.md) are written this way with both. A screen provides or watches its state and passes plain values and callbacks to a view, a widget that imports no package of a state manager. State that several screens read, such as who uses the app, is provided around the root of the app with `bloc`, and is a provider of its own with `riverpod`.
