Skip to content

Installation

View raw .md

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#

bash
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#

bash
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:

yaml
dependencies:
  magic_notifications:
    path: ./plugins/magic_notifications

CLI Installation Wizard#

Run the wizard from the Flutter project root, after plugin:install:

bash
dart run magic:dowel notifications:install

It asks for:

  1. OneSignal App ID: a UUID, from the OneSignal Dashboard. The wizard asks again until the format is valid.
  2. Platforms: it detects the platform directories the project has and asks to confirm each one.
  3. Web options (only when web is selected): an optional legacy Safari Web ID and the OneSignal notify button.
  4. Soft prompt: whether soft_prompt.enabled is written as true.

Non-Interactive Mode#

bash
dart run magic:dowel notifications:install \
  --non-interactive \
  --app-id 12345678-1234-1234-1234-123456789012 \
  --platforms android,ios,web
bash
# 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
bash
# 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:

dart
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() binds NotificationManager as a singleton under the 'notifications' key.
  • boot() registers the database channel, loads the persisted push intent, resolves the push driver named by notifications.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:

dart
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:

dart
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:

  1. Run cd ios && pod install --repo-update && cd ...
  2. Create an APNs key in the Apple Developer Portal (a .p8 file, its Key ID and your Team ID). One key covers Sandbox and Production, it is team-wide, and it downloads exactly once.
  3. Upload the .p8 key 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:

  1. App Group. In Xcode, add the App Groups capability to the Runner target with a container named group.<your.bundle.id>.onesignal.
  2. 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:

text
<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:

text
importScripts("https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.sw.js");

web/index.html, inside <head>:

html
<script src="https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.page.js" defer></script>
<script>
  window.OneSignalDeferred = window.OneSignalDeferred || [];
</script>
esc

Search index did not load.

No match for "".

Try a class like rounded-lg or a widget like WButton.