Skip to content

feat(android): support multiple simultaneous foreground services#6

Merged
ppamorim merged 2 commits intomasterfrom
feature/multiAndroidService
Apr 17, 2026
Merged

feat(android): support multiple simultaneous foreground services#6
ppamorim merged 2 commits intomasterfrom
feature/multiAndroidService

Conversation

@ppamorim
Copy link
Copy Markdown

@ppamorim ppamorim commented Apr 17, 2026

Summary

  • Introduces a multi-service architecture for Android, allowing multiple independent foreground services to run simultaneously, each with its own notification, task handler, Dart isolate, and SharedPreferences scope.
  • Adds FlutterForegroundServiceBase (abstract), FlutterForegroundServiceRegistry, and ForegroundServiceRuntime on the Android side; FlutterForegroundTaskController.of(id) on the Dart side.
  • Makes the default ForegroundService manifest entry optional — the library validates the manifest at runtime and throws a clear error (or silently skips at boot) if a service class is not declared.

Changes

Android (Kotlin)

  • FlutterForegroundServiceBase — new abstract base class containing all service logic; ForegroundService becomes a thin serviceId="default" subclass.
  • FlutterForegroundServiceRegistry — singleton mapping serviceIdService class, with auto-registration of the default in a static initializer (available at cold boot). Includes isServiceDeclaredInManifest() for runtime validation.
  • ForegroundServiceRuntime — per-id running state, task references, and lifecycle listeners (replaces companion-object statics).
  • ForegroundServiceManager — all methods accept serviceId; start() validates the manifest before proceeding.
  • RebootReceiver / RestartReceiver — iterate registered ids, skip services not declared in the manifest.
  • All model companions — accept optional serviceId for scoped SharedPreferences.
  • ForegroundTask — sends onServiceIdSet to the Dart isolate so sendDataToMain routes correctly.
  • MethodCallHandlerImpl — extracts serviceId from method channel args.

Dart

  • FlutterForegroundTaskController — instance-based API (init, start, stop, update, restart, communication ports, storage) scoped by service id.
  • FlutterForegroundTask — static API now delegates to FlutterForegroundTaskController.of('default') for full backward compatibility.
  • currentServiceId — set by the native layer on isolate start; sendDataToMain auto-routes to the correct port.
  • Platform interface / method channelserviceId parameter threaded through all service methods.

Documentation

  • documentation/multiple_services.md — full guide: Kotlin subclassing, manifest entries, Application.onCreate registration, Dart controller usage, communication ports, backward-compatibility table, limitations.
  • documentation/android_shared_preferences_usage.md — updated architecture diagram and class references.
  • README.md — new "Multiple services (Android)" section linking to the guide.

Backward compatibility

Existing single-service apps require zero changes. All public API signatures are preserved. SharedPreferences file names for the "default" id are identical to the pre-refactor layout.

Test plan

  • All 81 existing Dart tests pass (flutter test)
  • flutter analyze clean (only pre-existing deprecation infos)
  • Build and run the example app on a physical Android device with the default single-service setup
  • Add a second <service> subclass to the example, verify both services run simultaneously with independent notifications
  • Remove the default <service> entry from the manifest, verify startService throws IllegalStateException with a helpful message
  • Verify autoRunOnBoot works for declared services and is skipped for undeclared ones
  • Verify sendDataToMain delivers data to the correct UI-side controller for non-default services

Introduces a flexible multi-service architecture while preserving full
backward compatibility for existing single-service apps.

Android:
- Add `FlutterForegroundServiceBase` abstract class; `ForegroundService`
  becomes a thin `serviceId="default"` subclass.
- Add `FlutterForegroundServiceRegistry` (singleton) with a static-init
  default registration so `RebootReceiver`/`RestartReceiver` can resolve
  services at cold boot, before any Flutter engine attaches.
- Add `ForegroundServiceRuntime` to manage per-id running state, task
  references, and lifecycle listeners, replacing companion-object statics.
- Scope all `SharedPreferences` files by `serviceId` via
  `ForegroundTaskStorageProvider`; default id uses the unchanged legacy
  file names (zero migration for existing installs).
- Thread `serviceId` through `ForegroundServiceManager`, model companions,
  `RestartReceiver`, `RebootReceiver`, `ForegroundServiceUtils`, and
  `MethodCallHandlerImpl`.
- Send `onServiceIdSet` over the background channel immediately before
  `onStart` so each Dart isolate knows its own service id.

Dart:
- Add `FlutterForegroundTaskController.of(id)` for explicit per-service
  management (init/start/stop/update/restart, communication ports, storage).
- Expose `FlutterForegroundTask.currentServiceId` (set by native layer on
  isolate start) and make `sendDataToMain` route automatically to the
  correct controller, fixing silent data loss for non-default services.
- `FlutterForegroundTask` static API delegates to
  `FlutterForegroundTaskController.of('default')` — no breaking changes.
- Add `serviceId` parameter to platform-interface and method-channel
  implementations.

Docs:
- Add `documentation/multiple_services.md` covering subclassing, manifest
  entries, `Application.onCreate` registration, Dart controller usage,
  isolate model, and backward-compatibility table.
- Update `documentation/android_shared_preferences_usage.md` with the
  serviceId-scoped diagram and corrected class references.
- Add "Multiple services (Android)" entry to README.
Add manifest validation before starting any foreground service. The
library now checks PackageManager.getServiceInfo() to confirm a service
class is declared in AndroidManifest.xml before attempting to start it.

- Add FlutterForegroundServiceRegistry.isServiceDeclaredInManifest()
- Gate ForegroundServiceManager.start() with a manifest check that
  throws IllegalStateException with a helpful message when the entry
  is missing
- Gate RebootReceiver to skip registered services not in the manifest
- Gate RestartReceiver to bail early for undeclared services
- Update documentation to reflect that the default <service> entry is
  optional when only custom service subclasses are used
@ppamorim ppamorim force-pushed the feature/multiAndroidService branch from 936cfd0 to c4647ed Compare April 17, 2026 20:01
@sonarqubecloud
Copy link
Copy Markdown

@ppamorim ppamorim merged commit bb7fb3a into master Apr 17, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant