Skip to content

SDKs

Flutter

On this page

Notifiable registers a Flutter app with Notifiable: connect with a public key, ask for notification permission, identify the user, list their inbox, record opens and taps, and track events.

Import package:notifiable/notifiable.dart only. On iOS the package uses APNs; on Android it uses FCM. The app does not pass a push port.

Install

dependencies:
  notifiable:
    path: ../flutter   # or a git dependency pointing at the flutter/ directory

Publishing to pub.dev is a later step.

iOS: iOS 13+. Enable the notifications capability. The plugin receives the APNs token through Flutter’s application delegate — no AppDelegate swizzling.

Android: Add google-services.json and apply the Google Services plugin when you want an FCM token. If Firebase is not initialized, the token is empty and connect still saves the subscriber. A missing google-services.json does not crash startup.

Integrate

Wrap the app in Notifiable. It builds the client and calls start() on mount.

import 'package:notifiable/notifiable.dart';

Notifiable(
  publicKey: 'ntf_pub_...',
  deepLinks: const [
    DeepLink(path: '/orders', label: 'Orders'),
    DeepLink(path: '/inbox', label: 'Inbox'),
  ],
  onDeepLink: (url) {
    // Open this route with your navigator.
  },
  child: const Home(),
)
Argument Required Description
publicKey yes* Key from the Notifiable dashboard. Development key while building; production key for the store build.
deepLinks no Routes Notifiable can link to: DeepLink(path:, label:).
onDeepLink no Called with a notification url (e.g. development toast tap).
logger no debug / info / warn / error callbacks.
storage no Custom store. Defaults to shared_preferences.
client no Existing NotifiableClient. Ignores publicKey.
child yes App tree.

*Required unless you pass client.

Read the client from any child:

final notifiable = Notifiable.of(context);

Text(notifiable.snapshot.ready ? 'Connected' : 'Connecting…');

Or build the client yourself (no widget):

final notifiable = NotifiableClient(
  publicKey: 'ntf_pub_...',
  deepLinks: const [DeepLink(path: '/orders', label: 'Orders')],
  onDeepLink: (url) { /* navigate */ },
);

await notifiable.start();
await notifiable.requestPermission();
await notifiable.identify(email: '[email protected]', externalId: 'user_123');

Permission

FilledButton(
  onPressed: () async {
    await Notifiable.of(context).requestPermission();
  },
  child: const Text('Enable notifications'),
)

Or ask from the Notifiable dashboard — the next connect opens the system prompt. On iOS, provider is apns (lowercase hex token). On Android, provider is fcm. A granted permission with an empty token is saved as not_determined on connect.

NotifiableClient API

Wait until snapshot.ready is true before identify, inbox, notification, openNotification, clickDelivery, or track.

State (snapshot)

Field Meaning
ready Connect finished and the subscriber was saved.
permission granted / denied / notDetermined (wire: not_determined).
subscriber Device/person record, or null until connect.
clientConfig Dashboard app record, or null.
deepLinks Saved routes, or null.
devIndicator / diagnostics Development mark and crash-sharing choices.
error Last NotifiableError, or null.

Widgets that call Notifiable.of rebuild when the snapshot changes.

Methods

start() — Connects. The widget calls this on mount.

requestPermission() — System prompt, then save. Returns the new permission.

identify(...) — Attach a signed-in user. Pass at least one of email, externalId, firstName, lastName (email/externalId preferred). Blank email or external id is rejected.

await notifiable.identify(
  email: '[email protected]',
  externalId: 'user_123',
  firstName: 'Ada',
  lastName: 'Lovelace',
);

inbox({InboxQuery? query}) — One page of notifications (normally 20).

final page = await notifiable.inbox(
  query: const InboxQuery(page: 1, opened: false),
);

notification(id) / openNotification(id) / clickDelivery(deliveryId) — Load, mark opened, record tap.

track(name, {properties}) — Event for the connected subscriber.

await notifiable.track(
  'order.shipped',
  properties: const {
    'order': EventProperty.string('1001'),
    'express': EventProperty.bool(true),
  },
);

setDevIndicator / setDiagnostics — Development mark and diagnostics. No-ops for a production key.

Development vs production

Development key Production key
First launch Sheet to show/hide the Notifiable mark No sheet
On screen Draggable mark (bundle id / package check) Nothing overlaid
Notifications Recent unread can appear as top toasts while foreground + granted System displays them
Diagnostics Failed SDK calls can be shared Nothing sent

On the iOS simulator, ⌘N shows the mark again.

Errors

Failed calls throw NotifiableError. snapshot.error is the last failure from start, requestPermission, and identify.

Code Meaning
invalid_config Blank public key, or identify without a field.
invalid_public_key Key rejected.
network Request failed or unexpected JSON.
validation_failed Rejected value.
subscriber_not_found Connect not finished, or subscriber gone.
subscriber_limit Project cannot add another subscriber.
rate_limited Too many requests.
missing_token Granted permission but no device token (connect saves not_determined).
missing_storage Session storage could not be opened.
missing_provider Notifiable.of outside the widget.
not_implemented Reserved.

Example

example/ stores a public key, connects, and opens /orders or /inbox from notification urls. From example/: flutter run (after iOS Push capability and, on Android, google-services.json).

Tests

cd flutter && flutter test

For AI agents

Agents should read this page as Markdown: /flutter.md. Start from /llms.txt or /ai-agents.md when the framework is unknown.

Rules

  1. Import only package:notifiable/notifiable.dart. Do not deep-import lib/src.
  2. Mount one Notifiable(...) around the app (or build NotifiableClient and call start()).
  3. Pass publicKey, optional deepLinks, and onDeepLink that navigates with the app’s router.
  4. Wait for snapshot.ready before identify / inbox / track APIs. Call requestPermission() from a button.
  5. After sign-in, call identify with email and/or externalId.
  6. On Android, ensure Firebase / google-services.json only when an FCM token is required; connect still works without it.
  7. Do not invent hosts, env names, or CORS settings. Development key for local builds; production key for release.

Integration prompt

Copy this into a coding agent when wiring the SDK into an existing Flutter app:

Integrate the Notifiable Flutter package (package:notifiable/notifiable.dart) into this Flutter project.

1. Add the notifiable dependency (path or git to the flutter/ directory of notifiable-sdks).
2. On iOS, enable the notifications capability. On Android, add google-services.json and the Google Services plugin if the app should receive FCM tokens; do not crash if Firebase is missing.
3. Wrap the root MaterialApp / navigator in Notifiable(publicKey: ..., deepLinks: ..., onDeepLink: ..., child: ...).
4. Pass publicKey from the project’s config (development key for debug, production key for release).
5. Implement onDeepLink to push the notification path with this app’s Navigator / go_router / auto_route.
6. After sign-in, when snapshot.ready is true, call identify(email: ..., externalId: ...).
7. Add an “Enable notifications” button that calls requestPermission().
8. If there is an inbox screen, use inbox(), openNotification(id), and clickDelivery(deliveryId).
9. Do not explain CORS, API hosts, or environments — only the public key. Keep changes minimal and match existing project patterns.