notiblast_sdk 0.8.0 copy "notiblast_sdk: ^0.8.0" to clipboard
notiblast_sdk: ^0.8.0 copied to clipboard

Flutter push-notification SDK for the NotiBlast backend: FCM device registration, tray display, inline reply, and chat-style conversation notifications.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:notiblast_sdk/notiblast_sdk.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // print() (not debugPrint) so SDK logs reach Console.app in Release builds.
  // ignore: avoid_print
  NotiBlast.setLogHandler((record) => print(record));
  // Done before runApp, not behind a button: a reply to a closed app (iOS)
  // launches it in the background without a UI, and the reply only reaches
  // onReply once setReplyHandler() has run. Same rule as canReply in
  // ios/NotificationService/NotificationService.swift.
  await NotiBlast.initAppId('smoke-test-app-id');
  await NotiBlast.Notifications.setReplyHandler(
    onReply,
    canReply: canReply,
    actionTitle: 'Balas',
    placeholder: 'Tulis pesan',
  );
  runApp(const MyApp());
}

/// Receives the text a user typed into a notification's reply field.
///
/// Must be a top-level function with this annotation. On Android it runs in
/// a background isolate, so it can't touch the page below — check the device
/// log (`flutter logs` / logcat) for the line it prints.
@pragma('vm:entry-point')
Future<void> onReply(NotiBlastReplyEvent event) async {
  debugPrint(
    '[NotiBlast example] reply: text="${event.text}" '
    'data=${event.notification.data}',
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'NotiBlast SDK Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SmokeTestPage(),
    );
  }
}

/// Decides which notifications get the reply action: here, only group chats
/// (`group_key` starting with `groupChat-`).
///
/// Same rules as [onReply]: top-level, annotated, and it also runs in a
/// background isolate — so it only reads the notification.
@pragma('vm:entry-point')
bool canReply(NotiBlastNotification notification) =>
    notification.groupId?.startsWith('groupChat-') ?? false;

/// Decides which notifications are shown as a conversation (Android): here,
/// only group chats (`group_key` starting with `groupChat-`); everything else
/// stays a regular notification.
///
/// Same rules as [onReply]. The sender fields fall back to the title when the
/// payload has no `sender_id` / `sender_name`, so the dev push script works
/// as is.
@pragma('vm:entry-point')
NotiBlastConversation? toConversation(NotiBlastNotification notification) {
  final room = notification.groupId;
  if (room == null || !room.startsWith('groupChat-')) return null;
  final data = notification.data;
  final senderName =
      data['sender_name'] as String? ?? notification.title ?? 'Someone';
  return NotiBlastConversation(
    conversationId: room,
    conversationTitle: data['room_name'] as String? ?? 'Group chat',
    senderId: data['sender_id'] as String? ?? senderName,
    senderName: senderName,
    senderAvatarUrl: data['sender_avatar'] as String?,
    // Consumer picks the conversation icon; the dev push script sends `icon`.
    conversationAvatarUrl:
        data['room_avatar'] as String? ?? data['icon'] as String?,
    text: notification.body ?? '',
    isGroup: true,
  );
}

/// Manual smoke-test page for the NotiBlast SDK core flow — not a product
/// screen. Listeners are registered in [initState] (before any button is
/// pressed) and removed in [dispose]; buttons call the SDK in order
/// (initAppId -> requestPermission -> login -> logout / optOut / optIn) —
/// device registration and notifications both start at login(), which
/// registers the device if permission was granted in step 2. Results and
/// listener events are appended to the log below.
class SmokeTestPage extends StatefulWidget {
  const SmokeTestPage({super.key});

  @override
  State<SmokeTestPage> createState() => _SmokeTestPageState();
}

class _SmokeTestPageState extends State<SmokeTestPage> {
  final List<String> _log = [];

  @override
  void initState() {
    super.initState();
    NotiBlast.Notifications.addClickListener(_onClick);
    NotiBlast.Notifications.addForegroundListener(_onForeground);
    NotiBlast.Notifications.addRegistrationErrorListener(_onRegistrationError);
  }

  @override
  void dispose() {
    NotiBlast.Notifications.removeClickListener(_onClick);
    NotiBlast.Notifications.removeForegroundListener(_onForeground);
    NotiBlast.Notifications.removeRegistrationErrorListener(
      _onRegistrationError,
    );
    super.dispose();
  }

  void _onClick(NotiBlastClickEvent event) {
    final n = event.notification;
    _appendLog(
      'click: ${n.title} / group=${n.groupId} '
      '/ image=${n.imageUrl} / clickAction=${n.clickAction} '
      '/ additionalData=${n.additionalData} / data=${n.data}',
    );
  }

  void _onForeground(NotiBlastNotification notification) {
    _appendLog(
      'foreground notification: ${notification.title} '
      '/ group=${notification.groupId} / image=${notification.imageUrl}',
    );
    // The SDK no longer auto-shows foreground notifications — call this to
    // display it in the tray.
    notification.showNotification();
  }

  void _onRegistrationError(DeviceRegistrationException error) {
    _appendLog('registration error: $error');
  }

  void _appendLog(String message) {
    setState(() => _log.insert(0, message));
  }

  Future<void> _initAppId() async {
    try {
      await NotiBlast.initAppId('smoke-test-app-id');
      _appendLog('initAppId: ok');
    } catch (e) {
      _appendLog('initAppId failed: $e');
    }
  }

  // main() already runs initAppId() + setReplyHandler (groupChat- only).
  // initAppId() clears the stored reply settings, so after pressing
  // "1. initAppId" press one of these again (before "3. login").
  Future<void> _setReplyHandlerAll() async {
    try {
      await NotiBlast.Notifications.setReplyHandler(onReply);
      _appendLog('setReplyHandler (all notifications): ok');
    } catch (e) {
      _appendLog('setReplyHandler failed: $e');
    }
  }

  Future<void> _setReplyHandlerGroupChatOnly() async {
    try {
      await NotiBlast.Notifications.setReplyHandler(
        onReply,
        canReply: canReply,
        actionTitle: 'Balas',
        placeholder: 'Tulis pesan',
      );
      _appendLog('setReplyHandler (groupChat- only): ok');
    } catch (e) {
      _appendLog('setReplyHandler failed: $e');
    }
  }

  // Also cleared by initAppId(): press after "1. initAppId", before "3. login".
  Future<void> _setConversationHandler() async {
    try {
      await NotiBlast.Notifications.setConversationHandler(
        toConversation,
        style: const NotiBlastConversationStyle(
          maxMessages: 5,
          selfName: 'Saya',
        ),
      );
      _appendLog('setConversationHandler (groupChat- only): ok');
    } catch (e) {
      _appendLog('setConversationHandler failed: $e');
    }
  }

  // Same room as the group_key in scripts/send_push.dart.
  Future<void> _clearConversation() async {
    try {
      await NotiBlast.Notifications.clearConversation(
        'groupChat-691d86229cec5cdf3009ba21',
      );
      _appendLog('clearConversation: ok');
    } catch (e) {
      _appendLog('clearConversation failed: $e');
    }
  }

  Future<void> _requestPermission() async {
    try {
      final granted = await NotiBlast.Notifications.requestPermission();
      _appendLog('requestPermission: $granted');
    } catch (e) {
      _appendLog('requestPermission failed: $e');
    }
  }

  Future<void> _login() async {
    try {
      await NotiBlast.login('smoke-test-user-id');
      _appendLog('login: ok');
    } catch (e) {
      _appendLog('login failed: $e');
    }
  }

  Future<void> _logout() async {
    try {
      await NotiBlast.logout();
      _appendLog('logout: ok');
    } catch (e) {
      _appendLog('logout failed: $e');
    }
  }

  Future<void> _optOut() async {
    try {
      await NotiBlast.Notifications.optOut();
      _appendLog('optOut: ok');
    } catch (e) {
      _appendLog('optOut failed: $e');
    }
  }

  Future<void> _optIn() async {
    try {
      await NotiBlast.Notifications.optIn();
      _appendLog('optIn: ok');
    } catch (e) {
      _appendLog('optIn failed: $e');
    }
  }

  Future<void> _subscribeTopic() async {
    try {
      await NotiBlast.Notifications.subscribeTopic('test-topic');
      _appendLog('subscribeTopic("test-topic"): ok');
    } catch (e) {
      _appendLog('subscribeTopic failed: $e');
    }
  }

  Future<void> _unsubscribeTopic() async {
    try {
      await NotiBlast.Notifications.unsubscribeTopic('test-topic');
      _appendLog('unsubscribeTopic("test-topic"): ok');
    } catch (e) {
      _appendLog('unsubscribeTopic failed: $e');
    }
  }

  /// Shows a grouped notification that disappears after 2 seconds (Android).
  /// Same room as scripts/send_push.dart: `toConversation` would match it, but
  /// a temporary notification is always plain, and the room's conversation
  /// and summary should stay untouched when it goes away.
  Future<void> _showTemporary() async {
    try {
      await const NotiBlastNotification(
        title: 'Reply not sent',
        body: 'Disappears in 2 seconds',
        data: {'replyFailed': true},
        groupId: 'groupChat-691d86229cec5cdf3009ba21',
      ).showNotification(timeoutAfter: const Duration(seconds: 2));
      _appendLog('showNotification(timeoutAfter: 2s): ok');
    } catch (e) {
      _appendLog('showNotification(timeoutAfter) failed: $e');
    }
  }

  Future<void> _clearAll() async {
    try {
      await NotiBlast.Notifications.clearAll();
      _appendLog('clearAll: ok');
    } catch (e) {
      _appendLog('clearAll failed: $e');
    }
  }

  Future<void> _removeGroupedNotifications() async {
    try {
      await NotiBlast.Notifications.removeGroupedNotifications('demo-group');
      _appendLog('removeGroupedNotifications("demo-group"): ok');
    } catch (e) {
      _appendLog('removeGroupedNotifications failed: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('NotiBlast SDK Smoke Test')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                ElevatedButton(
                  onPressed: _initAppId,
                  child: const Text('1. initAppId'),
                ),
                ElevatedButton(
                  onPressed: _requestPermission,
                  child: const Text('2. requestPermission'),
                ),
                ElevatedButton(
                  onPressed: _login,
                  child: const Text('3. login'),
                ),
                ElevatedButton(
                  onPressed: _setReplyHandlerAll,
                  child: const Text('reply: all'),
                ),
                ElevatedButton(
                  onPressed: _setReplyHandlerGroupChatOnly,
                  child: const Text('reply: groupChat-'),
                ),
                ElevatedButton(
                  onPressed: _setConversationHandler,
                  child: const Text('conversation: groupChat-'),
                ),
                ElevatedButton(
                  onPressed: _clearConversation,
                  child: const Text('clearConversation'),
                ),
                ElevatedButton(onPressed: _logout, child: const Text('logout')),
                ElevatedButton(onPressed: _optOut, child: const Text('optOut')),
                ElevatedButton(onPressed: _optIn, child: const Text('optIn')),
                ElevatedButton(
                  onPressed: _subscribeTopic,
                  child: const Text('subscribeTopic'),
                ),
                ElevatedButton(
                  onPressed: _unsubscribeTopic,
                  child: const Text('unsubscribeTopic'),
                ),
                ElevatedButton(
                  onPressed: _showTemporary,
                  child: const Text('temporary (2s)'),
                ),
                ElevatedButton(
                  onPressed: _clearAll,
                  child: const Text('clearAll'),
                ),
                ElevatedButton(
                  onPressed: _removeGroupedNotifications,
                  child: const Text('removeGroupedNotifications'),
                ),
              ],
            ),
            const Divider(height: 32),
            Expanded(
              child: ListView.builder(
                itemCount: _log.length,
                itemBuilder: (context, index) => Text(_log[index]),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
0
likes
0
points
95
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter push-notification SDK for the NotiBlast backend: FCM device registration, tray display, inline reply, and chat-style conversation notifications.

Homepage
Repository (GitLab)
View/report issues

License

unknown (license)

Dependencies

device_info_plus, firebase_core, firebase_messaging, flutter, flutter_local_notifications, http, meta, package_info_plus, shared_preferences

More

Packages that depend on notiblast_sdk

Packages that implement notiblast_sdk