Installation
Add Magic Notifications to a Magic app, register its provider, and wire the platform files a push needs to reach a device.
On this page
Quick Start#
flutter pub add magic_notifications
dart run magic:dowel plugin:install magic_notifications
dart run magic:dowel notifications:install
dart run magic:dowel notifications:doctor
| Step | What it does |
|---|---|
plugin:install |
Injects NotificationServiceProvider into lib/config/app.dart and registers the package's commands with dowel |
notifications:install |
Writes lib/config/notifications.dart and the platform files, then wires the config into lib/main.dart |
notifications:doctor |
Reports what is still missing |
Dependency Setup#
flutter pub add magic_notifications
The package builds on magic, magic_socket (realtime delivery), dowel (the CLI), onesignal_flutter and wind_kit; pub resolves all of them. For local development inside a monorepo, point the dependency at the checkout:
dependencies:
magic_notifications:
path: ./plugins/magic_notifications
CLI Installation Wizard#
Run the wizard from the Flutter project root, after plugin:install:
dart run magic:dowel notifications:install
It asks for:
- OneSignal App ID: a UUID, from the OneSignal Dashboard. The wizard asks again until the format is valid.
- Platforms: it detects the platform directories the project has and asks to confirm each one.
- Web options (only when web is selected): an optional legacy Safari Web ID and the OneSignal notify button.
- Soft prompt: whether
soft_prompt.enabledis written astrue.
Non-Interactive Mode#
dart run magic:dowel notifications:install \
--non-interactive \
--app-id 12345678-1234-1234-1234-123456789012 \
--platforms android,ios,web
# Web extras
dart run magic:dowel notifications:install \
--non-interactive \
--app-id 12345678-1234-1234-1234-123456789012 \
--platforms web \
--safari-web-id web.onesignal.auto.xxx \
--notify-button
# Disable the soft prompt and overwrite an existing config
dart run magic:dowel notifications:install \
--non-interactive \
--app-id 12345678-1234-1234-1234-123456789012 \
--no-soft-prompt \
--force
--app-id is required in non-interactive mode and an invalid UUID stops the command before anything is written. See the CLI reference for every flag.
What the CLI Produces#
| Artifact | Path |
|---|---|
| Notification config | lib/config/notifications.dart (skipped with a warning when it exists, unless --force) |
| Android permission | android/app/src/main/AndroidManifest.xml (POST_NOTIFICATIONS) |
| iOS background mode | ios/Runner/Info.plist (UIBackgroundModes gains remote-notification) |
| iOS entitlements | ios/Runner/Runner.entitlements (aps-environment development) and ios/Runner/RunnerRelease.entitlements (production), both created if absent |
| iOS build setting | ios/Runner.xcodeproj/project.pbxproj (CODE_SIGN_ENTITLEMENTS per configuration) |
| Web service worker | web/OneSignalSDKWorker.js |
| Web SDK script | web/index.html (injected once) |
| Provider injection | lib/config/app.dart |
| Config factory | lib/main.dart (import plus a configFactories entry) |
Service Provider Registration#
plugin:install has already edited lib/config/app.dart. For a manual setup, add the import and the provider entry:
import 'package:magic_notifications/magic_notifications.dart';
Map<String, dynamic> get appConfig => {
'app': {
'providers': [
// ... other providers
(app) => NotificationServiceProvider(app),
],
},
};
NotificationServiceProvider runs two phases during framework boot:
register()bindsNotificationManageras a singleton under the'notifications'key.boot()registers the database channel, loads the persisted push intent, resolves the push driver named bynotifications.push.driver, registers the push channel and initialises the driver when an App ID is set, then reconciles the device's push identity once. It starts no polling.
See Service Provider for the full boot sequence.
Config Factory in main.dart#
The notification config reaches Magic.init as a config factory:
import 'package:flutter/widgets.dart';
import 'package:magic/magic.dart';
import 'config/app.dart'; // The host's own config: declares `appConfig`.
import 'config/notifications.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Magic.init(
configFactories: [
() => appConfig,
() => notificationConfig, // added by notifications:install
],
);
runApp(const MyApp()); // `MyApp` is the host's own root widget.
}
The notificationConfig getter lives in lib/config/notifications.dart, written by notifications:install or notifications:publish. Its core shape:
Map<String, dynamic> get notificationConfig => {
'notifications': {
'push': {
'driver': 'onesignal',
'app_id': '12345678-1234-1234-1234-123456789012',
'service_worker_path': 'OneSignalSDKWorker.js',
'service_worker_scope': '/onesignal/',
'notify_button_enabled': false,
},
'database': {
'enabled': true,
'polling_interval': 30,
},
'soft_prompt': {
'enabled': true,
},
},
};
The generated file carries more keys than this and comments on each; Configuration documents all of them. To keep the App ID out of source control, read it with env('ONESIGNAL_APP_ID') instead of a literal, as described there.
Platform Setup Summary#
iOS#
The CLI writes the project-file markers a push needs, without opening Xcode: UIBackgroundModes gains remote-notification in Info.plist, aps-environment is declared in both entitlements files (development for Debug and Profile, production for Release), and CODE_SIGN_ENTITLEMENTS is pointed at the right file per build configuration. Xcode never reads an entitlements file that no build setting names, which is why the doctor checks the setting and not just the file.
What stays manual, because it needs an Apple or OneSignal account rather than a project-file edit:
- Run
cd ios && pod install --repo-update && cd ... - Create an APNs key in the Apple Developer Portal (a
.p8file, its Key ID and your Team ID). One key covers Sandbox and Production, it is team-wide, and it downloads exactly once. - Upload the
.p8key to the OneSignal Dashboard (Settings, Platforms, Apple iOS (APNs)) with the Key ID, your Team ID and your bundle identifier.
The installer declines to repoint a project that already names a different entitlements file, one whose configurations are none of Debug, Profile or Release, and one whose project file it cannot rewrite. Each case prints what to do by hand; Shipping push in a release build covers them.
The Notification Service Extension
Push works without the next two steps, which is why they need saying: a build with no extension delivers notifications normally and quietly reports no confirmed deliveries, no rich media and no badge counts. notifications:doctor warns when either half is missing. Neither can be automated from a pub package, because both add or change an Xcode target:
- App Group. In Xcode, add the App Groups capability to the Runner target with a container named
group.<your.bundle.id>.onesignal. - Notification Service Extension. File, New, Target, Notification Service Extension, named
OneSignalNotificationServiceExtension. Add the same App Group to that new target and follow OneSignal's current Flutter SDK setup for the extension's source and deployment target.
The two halves fail independently. An extension with no shared App Group still gives rich media but no confirmed delivery, because the container is how the extension hands what it saw back to the app.
Android#
The CLI injects POST_NOTIFICATIONS into android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
Web#
The CLI writes the service worker and injects the SDK script. Done by hand:
web/OneSignalSDKWorker.js:
importScripts("https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.sw.js");
web/index.html, inside <head>:
<script src="https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.page.js" defer></script>
<script>
window.OneSignalDeferred = window.OneSignalDeferred || [];
</script>
Related Documentation#
- Configuration: every
notifications.*key. - CLI Reference: the full command and flag list.
- Shipping push in a release build: the differences a development build hides.
- Service Provider: what
boot()does. - Channels and Drivers: sending and the push SDK layer.
- Laravel Backend Setup: the API this package talks to.
- Magic installation: the framework this plugin extends.
- Realtime delivery with magic_socket: the socket layer behind
Notify.startRealtime.