appcraft_notifications 0.0.1 copy "appcraft_notifications: ^0.0.1" to clipboard
appcraft_notifications: ^0.0.1 copied to clipboard

Firebase Cloud Messaging and local notifications behind one API: foreground copies, taps, cold start and the FCM token, with no backend assumptions.

example/lib/main.dart

import 'dart:async';

import 'package:appcraft_notifications/appcraft_notifications.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';

/// The channel of the application. Its id must match
/// `com.google.firebase.messaging.default_notification_channel_id` in the
/// Android manifest.
const AndroidNotificationChannel channel = AndroidNotificationChannel(
  'appcraft_notifications_example',
  'Example',
  description: 'Notifications of the example application',
  importance: Importance.max,
);

/// Public key of the Web Push certificate of the Firebase project.
///
/// Required to get a token on the web and ignored on the other platforms. Take
/// it from Project settings → Cloud Messaging → Web configuration.
const String vapidKey = 'YOUR_WEB_PUSH_CERTIFICATE_KEY';

/// Handles a message that arrived while the application is in the background.
///
/// A top-level function annotated with `@pragma('vm:entry-point')`: on Android
/// it runs in a separate isolate raised from scratch, so Firebase has to be
/// initialized here again and nothing of the running application is available.
/// This is the place to store the message; deduplicate by
/// [RemoteMessage.messageId], because the same message reaches the tap handler
/// as well.
///
/// Not called on the web at all — there the background handler is
/// `web/firebase-messaging-sw.js`.
@pragma('vm:entry-point')
Future<void> handleBackgroundMessage(RemoteMessage message) async {
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  debugPrint('background message ${message.messageId}');
}

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage);

  final AppCraftNotifications notifications =
      await AppCraftNotifications.create(
        androidChannel: channel,
        androidIcon: 'ic_notification',
        vapidKey: vapidKey,
        debugLogs: true,
      );

  runApp(ExampleApp(notifications: notifications));
}

/// The example application.
class ExampleApp extends StatelessWidget {
  /// Creates the application around a ready [notifications].
  const ExampleApp({required this.notifications, super.key});

  /// The instance the application owns and passes around itself.
  final AppCraftNotifications notifications;

  @override
  Widget build(BuildContext context) => MaterialApp(
    title: 'appcraft_notifications',
    theme: ThemeData(colorSchemeSeed: Colors.amber),
    home: HomePage(notifications: notifications),
  );
}

/// Shows everything the package reports.
class HomePage extends StatefulWidget {
  /// Creates the page.
  const HomePage({required this.notifications, super.key});

  /// The instance of the package.
  final AppCraftNotifications notifications;

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final List<String> _log = <String>[];

  StreamSubscription<AppCraftNotification>? _messages;
  StreamSubscription<AppCraftNotification>? _taps;
  StreamSubscription<String>? _tokenChanges;

  String? _token;

  @override
  void initState() {
    super.initState();

    _messages = widget.notifications.messages.listen(
      (AppCraftNotification notification) =>
          _add('message: ${notification.title} ${notification.data}'),
    );

    // Navigation by a payload is the responsibility of the application: the
    // package knows nothing about routes.
    _taps = widget.notifications.taps.listen(
      (AppCraftNotification notification) =>
          _add('tap: ${notification.data['route'] ?? notification.data}'),
    );

    _tokenChanges = widget.notifications.tokenChanges.listen((String token) {
      // Register the new token on the server, revoke the previous one only
      // after that succeeds, and only then overwrite the stored value.
      setState(() => _token = token);
      _add('token changed');
    });

    // The launch notification is taken when the navigator already exists.
    final AppCraftNotification? launch = widget.notifications
        .takeLaunchNotification();
    if (launch != null) {
      _add('launched from: ${launch.title} ${launch.data}');
    }
  }

  @override
  void dispose() {
    _messages?.cancel();
    _taps?.cancel();
    _tokenChanges?.cancel();
    super.dispose();
  }

  void _add(String line) => setState(() => _log.insert(0, line));

  Future<void> _requestPermission() async {
    final AuthorizationStatus status = await widget.notifications
        .requestPermission();
    _add('permission: ${status.name}');
  }

  Future<void> _getToken() async {
    final String? token = await widget.notifications.getToken();
    setState(() => _token = token);
    _add(token == null ? 'no token yet' : 'token received');
  }

  Future<void> _show() => widget.notifications.show(
    title: 'Local notification',
    body: 'Shown by the application itself',
    data: const <String, String>{'route': '/orders/1'},
  );

  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(title: const Text('appcraft_notifications')),
    body: Column(
      children: <Widget>[
        Padding(
          padding: const EdgeInsets.all(16),
          child: Wrap(
            spacing: 8,
            runSpacing: 8,
            children: <Widget>[
              FilledButton(
                onPressed: _requestPermission,
                child: const Text('Request permission'),
              ),
              FilledButton(
                onPressed: _getToken,
                child: const Text('Get token'),
              ),
              FilledButton(onPressed: _show, child: const Text('Show local')),
            ],
          ),
        ),
        if (_token != null)
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16),
            child: SelectableText(_token!, maxLines: 2),
          ),
        const Divider(),
        Expanded(
          child: ListView.builder(
            itemCount: _log.length,
            itemBuilder: (BuildContext context, int index) =>
                ListTile(dense: true, title: Text(_log[index])),
          ),
        ),
      ],
    ),
  );
}
0
likes
150
points
52
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Firebase Cloud Messaging and local notifications behind one API: foreground copies, taps, cold start and the FCM token, with no backend assumptions.

Repository (GitHub)
View/report issues

Topics

#firebase #notifications #push

License

MIT (license)

Dependencies

firebase_messaging, flutter, flutter_local_notifications

More

Packages that depend on appcraft_notifications