Infrastructure SDKs
background_sdk Free
- Tier
- Freeversion 0.1.4
- Command
archipelago generate background_sdk- Generates
infrastructure/background_sdk_apiinfrastructure/background_sdk_workmanager
Background work on WorkManager. Features register task handlers in a registry, and each task run gets its own DI container on the worker isolate, torn down when the run ends.
Variables
This brick has no generation-time variables.
Install
Interactive
archipelago generate background_sdkNon-interactive (CI)
archipelago generate background_sdk --config my_config.jsonWhy This Exists
The OS starts a background task on an isolate without the app. On that isolate:
GetIt.instanceis empty. A class that reads it throwsObject/factory not registered, at runtime, in release, only when the task fires.- A database connection opened by the UI isolate cannot be used. The task must open its own.
- A connection the task forgets to close can hold the database's write lock and block the UI isolate.
So a class the task runs must take its collaborators through its constructor, and the task needs its own short-lived container. This brick generates that composition root, so the app writes only the handler body.
Generated Files
| Path | Purpose |
|---|---|
infrastructure/background_sdk_api/ | BackgroundTaskRegistry, BackgroundTaskHandler, BackgroundTaskResult, BackgroundScheduler. Features depend on this package only. |
infrastructure/background_sdk_workmanager/ | BackgroundWorker (one run in its own container), runWorkmanagerDispatcher, WorkmanagerBackgroundScheduler |
apps/<app>/lib/background/background_dispatcher.dart | The @pragma('vm:entry-point') dispatcher, setupBackgroundDi and registerBackgroundTasks. You edit these two functions. |
apps/<app>/lib/host/initializers/background_work_initializer.dart | Pre-launch: registers the dispatcher with WorkManager and BackgroundScheduler in getIt |
apps/<app>/test/background/background_dispatcher_test.dart | Fails if the composition root reads the app-wide GetIt |
The post-gen hook adds both packages to the workspace and the app's pubspec.yaml, and adds BackgroundWorkInitializer to the pre_launch_initializers marker region.
How a Task Runs
sequenceDiagram
participant OS as WorkManager (OS)
participant D as backgroundDispatcher
participant W as BackgroundWorker
participant R as BackgroundTaskRegistry
participant DI as Task container
participant H as Feature handler
OS->>D: start isolate, executeTask(task, input)
D->>W: run(task, input)
W->>R: registerBackgroundTasks(registry)
W->>R: handlerFor(task)
alt no handler
W-->>D: failure (error reported)
else handler found
W->>DI: asNewInstance() + setupBackgroundDi(di)
W->>H: handler(di, input)
H-->>W: success / retry / failure (a throw is failure)
W->>DI: reset() in finally, dispose callbacks run
W-->>D: result
end
D-->>OS: true = success, false = retry, throw = failure
| Situation | Result | Error reported |
|---|---|---|
Handler returns success | success | no |
Handler returns retry | retry, with the OS backoff | no |
Handler returns failure | failure, no retry | no |
| Handler or setup throws | failure, no retry | yes |
| No handler for the task | failure, no retry | yes |
A throw is treated as a bug, not a transient error, so it is not retried. Catch transient errors (no network, a locked file) in the handler and return retry.
Errors go to BackgroundWorker's onError, which logs through dart:developer by default. To send them to crash reporting, pass your own onError in background_dispatcher.dart.
Writing a Handler
In the feature, export a registration function. It depends on background_sdk_api only:
// features/sync/sync_impl/lib/src/background/sync_background_tasks.dart
const syncTask = 'sync.pull';
void registerSyncBackgroundTasks(BackgroundTaskRegistry registry) {
registry.register(syncTask, (di, input) async {
final pull = PullChanges(di<SyncApi>(), di<AppDatabase>());
try {
await pull();
return BackgroundTaskResult.success;
} on SocketException {
return BackgroundTaskResult.retry;
}
});
}In the app, register what the handlers need and call the feature's function:
// apps/<app>/lib/background/background_dispatcher.dart
Future<void> setupBackgroundDi(GetIt di) async {
di.registerSingletonAsync<AppDatabase>(
AppDatabase.openInBackground,
dispose: (db) => db.close(),
);
di.registerLazySingleton<SyncApi>(() => SyncApi(Dio()));
await di.allReady();
}
void registerBackgroundTasks(BackgroundTaskRegistry registry) {
registerSyncBackgroundTasks(registry);
}Schedule from the main isolate through the port, never through Workmanager directly:
await getIt<BackgroundScheduler>().schedulePeriodic(
syncTask,
const Duration(hours: 1),
);The task name is also WorkManager's unique name, so scheduling a pending task again replaces it, and iOS periodic tasks reach the same handler.
Rules for code the task runs
- Never read
getItorGetIt.instance. Resolve fromdiin the handler, then pass objects through constructors. - Register only isolate-safe things in
setupBackgroundDi: no widgets, noBuildContext, no plugin that needs an Activity (pickers, permission prompts, in-app purchase). - Open the database on this isolate. Give every resource a
dispose:so the reset closes it.
Platform setup
The brick does not edit native projects:
- Android: no setup for one-off and periodic tasks.
- iOS: periodic and processing tasks need
UIBackgroundModes(fetch,processing) and the task names inBGTaskSchedulerPermittedIdentifiersinInfo.plist. See the workmanager iOS setup.
Limits
- The generated architecture test checks the files in
lib/background/and the two packages. It does not follow every class a handler reaches. Keep handler classes constructor-injected. - There is no database brick yet.
setupBackgroundDishows where to open one.