Service Provider
On this page
Table of Contents#
- Overview
- Two-Phase Bootstrap Pattern
- register() — IoC Binding
- boot() — Push Driver Initialization
- Config Loading from ConfigRepository
- IoC Integration
- Registering the Provider
Overview#
NotificationServiceProvider is the bootstrap entry point for the magic_notifications plugin. It extends the Magic Framework's ServiceProvider and is responsible for wiring NotificationManager into the IoC container and initializing the push driver from config.
class NotificationServiceProvider extends ServiceProvider {
NotificationServiceProvider(super.app);
@override
void register() { ... }
@override
Future<void> boot() async { ... }
}
Two-Phase Bootstrap Pattern#
The Magic Framework's service provider contract separates bootstrapping into two phases:
| Phase | Method | Timing | Purpose |
|---|---|---|---|
| 1 | register() |
Synchronous, early | Bind services into the IoC container |
| 2 | boot() |
Async, after all providers registered | Initialize services that depend on other bindings |
register() is called for all providers before boot() is called for any provider. This guarantees that by the time boot() runs, every binding registered by all other providers is already available.
register() — IoC Binding#
@override
void register() {
app.singleton('notifications', () => NotificationManager());
}
register() does one thing: bind the NotificationManager singleton under the 'notifications' key. Because NotificationManager is itself a Dart-level singleton (factory constructor), the IoC binding and the direct constructor call always return the same instance.
After register() completes, any code in the app can resolve the manager:
final manager = app.make<NotificationManager>('notifications');
boot() — Push Driver Initialization#
@override
Future<void> boot() async {
final manager = NotificationManager();
final pushDriver = Config.get<String>('notifications.push.driver');
if (pushDriver == 'onesignal') {
final driver = createOneSignalDriver();
manager.setPushDriver(driver);
final appId = Config.get<String>('notifications.push.app_id');
final safariWebId = Config.get<String>('notifications.push.safari_web_id');
final notifyButtonEnabled =
Config.get<bool>('notifications.push.notify_button_enabled') ?? false;
if (appId != null && appId.isNotEmpty) {
try {
await driver.initialize({
'app_id': appId,
'safari_web_id': safariWebId,
'notify_button_enabled': notifyButtonEnabled,
});
} catch (e) {
_log('Failed to initialize OneSignal: $e', isError: true);
}
}
}
}
The boot() sequence:
- Read
notifications.push.driverfromConfigRepository - If
'onesignal', callcreateOneSignalDriver()(returns platform-appropriate driver via conditional imports) - Call
manager.setPushDriver(driver)— makes the driver accessible viaNotificationManager().pushDriver - Read
app_id,safari_web_id,notify_button_enabledfrom config - Call
driver.initialize(config)ifapp_idis present and non-empty - Catch any initialization errors and log them without rethrowing — a failed push init should not prevent the app from starting
Config Loading from ConfigRepository#
Config.get is the Magic Framework's static accessor for the merged config map. The provider reads values using dot-notation keys:
// String values
Config.get<String>('notifications.push.driver') // 'onesignal'
Config.get<String>('notifications.push.app_id') // '12345678-...'
Config.get<String>('notifications.push.safari_web_id') // nullable
// Bool values
Config.get<bool>('notifications.push.notify_button_enabled') // false
Config.get<bool>('notifications.database.enabled') // true
Config.get<bool>('notifications.soft_prompt.enabled') // true
// Int values
Config.get<int>('notifications.database.polling_interval') // 30
All keys are null-safe — Config.get returns null when a key is absent or has the wrong type. The provider uses ?? false / ?? 0 where a non-null default is required.
The config values originate from the notificationConfig getter in lib/config/notifications.dart, which is registered as a config factory in Magic.init:
await Magic.init(
configFactories: [
() => appConfig,
() => notificationConfig,
],
);
IoC Integration#
Binding#
app.singleton('notifications', () => NotificationManager());
app.singleton registers a lazy singleton factory. The factory is called once on first resolution and the result is cached for subsequent make calls.
Resolution#
// Resolve by key
final manager = app.make<NotificationManager>('notifications');
// Or use the direct constructor (same instance)
final manager = NotificationManager();
Both resolution paths return identical references because NotificationManager() is a Dart-level singleton regardless of the IoC binding.
Registering the Provider#
Add NotificationServiceProvider to your app's provider list in lib/config/app.dart:
import 'package:magic_notifications/magic_notifications.dart';
final providers = [
// ... other framework providers first
(app) => NotificationServiceProvider(app),
];
The provider must be registered after any providers it depends on (e.g., ConfigServiceProvider, HttpServiceProvider). The Magic Framework's Config and Http services must be available when boot() runs.
Related