diff --git a/src/data/nav/pubsub.ts b/src/data/nav/pubsub.ts index 30454c9ded..cb9675e95b 100644 --- a/src/data/nav/pubsub.ts +++ b/src/data/nav/pubsub.ts @@ -268,6 +268,10 @@ export default { name: 'FCM', link: '/docs/push/getting-started/fcm', }, + { + name: 'Flutter', + link: '/docs/push/getting-started/flutter', + }, ], }, { diff --git a/src/images/content/screenshots/getting-started/flutter-push-getting-started-guide.png b/src/images/content/screenshots/getting-started/flutter-push-getting-started-guide.png new file mode 100644 index 0000000000..7b4462ce43 Binary files /dev/null and b/src/images/content/screenshots/getting-started/flutter-push-getting-started-guide.png differ diff --git a/src/pages/docs/push/getting-started/flutter.mdx b/src/pages/docs/push/getting-started/flutter.mdx new file mode 100644 index 0000000000..10949e0476 --- /dev/null +++ b/src/pages/docs/push/getting-started/flutter.mdx @@ -0,0 +1,526 @@ +--- +title: "Getting started: Push Notifications in Flutter" +meta_description: "Get started with Ably Push Notifications in Flutter. Learn how to register for push notifications on iOS and Android, activate push on your client, handle incoming notifications, and send push messages." +meta_keywords: "Push Notifications Flutter, Ably Push, FCM, APNs, Flutter push notifications, Firebase Cloud Messaging, Ably Push Notifications guide, realtime push Flutter, push notification example, Ably tutorial Flutter, device registration, push messaging, Dart" +--- + +This guide will get you started with Ably Push Notifications in a Flutter application targeting Android and iOS. + +You'll learn how to configure Firebase Cloud Messaging (FCM) for Android and APNs for iOS, register devices with Ably, send push notifications, subscribe to channel-based push, and handle incoming notifications. + +## Prerequisites + +1. [Sign up](https://ably.com/signup) for an Ably account. +2. Create a [new app](https://ably.com/accounts/any/apps/new), and create your first API key in the **API Keys** tab of the dashboard. +3. Your API key needs the `publish` and `subscribe` capabilities. For sending push notifications from your app, you'll also need the `push-admin` capability. +4. For channel-based push, add a rule for the channel with **Push notifications enabled** checked. In the dashboard left sidebar: **Configuration** → **Rules** → **Add** or **Edit** a rule, then enable the Push notifications option. See [channel rules](/docs/channels#rules) for details. +5. Install the [Flutter SDK](https://docs.flutter.dev/get-started/install). +6. Use a real device or an emulator with Google Play Services installed (required for FCM on Android), or a real iOS device or simulator (Xcode 14+) for APNs. + +### (Optional) Install Ably CLI + +Use the [Ably CLI](https://github.com/ably/cli) as an additional client to quickly test Pub/Sub features and push notifications. + +1. Install the Ably CLI: + + +```shell +npm install -g @ably/cli +``` + + +2. Run the following to log in to your Ably account and set the default app and API key: + + +```shell +ably login +``` + + +### Set up Firebase Cloud Messaging (Android) + +1. Go to the [Firebase Console](https://console.firebase.google.com/) and create a new project (or use an existing one). +2. Add an **Android** app to your Firebase project using your application's package name. +3. Download the `google-services.json` file and place it in your Android app module directory (`android/app/`). +4. In the Firebase Console, go to **Project configuration** → **Service accounts** and generate a new private key. Download the JSON file. +5. In the Ably dashboard left sidebar, navigate to **Push Notifications**. +6. Scroll to the **Configure push service for devices** section and press **Configure Push**. +7. Upload your Firebase service account JSON file and press **Save**. + +### Set up APNs (iOS) + +1. In the [Apple Developer portal](https://developer.apple.com), go to **Certificates, Identifiers & Profiles** → **Keys**. +2. Add a new key and check **Apple Push Notifications service (APNs)**, click **Register**. +3. Download the `.p8` file — you can only download it once. Note your **Key ID** and **Team ID**. +4. In the Ably dashboard left sidebar, navigate to **Push Notifications**. +5. Scroll to the **Configure push service for devices** section and press **Configure Push**. +6. Under **Apple Push Notification Service**, upload your `.p8` file, enter the **Key ID**, **Team ID**, and your app's **Bundle ID** (**Topic Header** field) and press **Save**. + +### Create a Flutter project + +Create a new Flutter project and navigate to the project folder: + + +```shell +flutter create ably_push_flutter --platforms android,ios +cd ably_push_flutter +``` + + +Add the following dependencies to your `pubspec.yaml`: + + +```flutter +dependencies: + flutter: + sdk: flutter + ably_flutter: ^1.2.35 + firebase_core: ^3.0.0 + firebase_messaging: ^15.0.0 +``` + + +Then run: + + +```shell +flutter pub get +``` + + +#### Configure Android + +Add the Google Services plugin to `android/build.gradle`: + + +```kotlin +buildscript { + dependencies { + classpath 'com.google.gms:google-services:4.4.2' + } +} +``` + + +Apply the plugin at the top of `android/app/build.gradle`: + + +```kotlin +apply plugin: 'com.google.gms.google-services' +``` + + +Add the `POST_NOTIFICATIONS` permission to `android/app/src/main/AndroidManifest.xml`: + + +```xml + +``` + + +You can use the [FlutterFire CLI](https://firebase.flutter.dev/docs/cli/) to generate the `firebase_options.dart` file, which provides platform-specific Firebase configuration automatically. Run the following command in your project directory: + + +```shell +flutterfire configure --platforms=android +``` + + +The `--platforms=android` flag limits configuration to Android, since `ably_flutter` on iOS uses APNs directly and does not require Firebase. + +#### Configure iOS + +Open `ios/Runner.xcworkspace` in Xcode and add the **Push Notifications** capability: + +1. Select the **Runner** target in Xcode. +2. Go to the **Signing & Capabilities** tab. +3. Click **+ Capability** and add **Push Notifications**. +4. Also add **Background Modes** and enable **Remote notifications**. + +All further code can be added to `lib/ably_service.dart` and `lib/main.dart`. + +## Step 1: Set up Ably + +Create a new file `lib/ably_service.dart` to manage your Ably Realtime client across the app: + + +```flutter +// lib/ably_service.dart +import 'package:ably_flutter/ably_flutter.dart' as ably; + +class AblyService { + static final AblyService _instance = AblyService._internal(); + late ably.Realtime _realtime; + + factory AblyService() { + return _instance; + } + + AblyService._internal(); + + Future init() async { + final clientOptions = ably.ClientOptions( + key: '{{API_KEY}}', // Use token authentication in production + clientId: 'push-tutorial-client', + ); + + _realtime = ably.Realtime(options: clientOptions); + } + + ably.Realtime get realtime => _realtime; +} +``` + + +Replace the contents of `lib/main.dart` to initialize Firebase and the Ably service on startup: + + +```flutter +// lib/main.dart +import 'dart:io'; +import 'package:ably_flutter/ably_flutter.dart' as ably; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'ably_service.dart'; +import 'firebase_options.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + // Initialize Firebase only on Android, as it's required for FCM. On iOS, Ably uses APNs directly. + if (Platform.isAndroid) { + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + } + await AblyService().init(); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Ably Push Tutorial', + theme: ThemeData(primarySwatch: Colors.blue), + home: const PushHomePage(), + ); + } +} + +class PushHomePage extends StatefulWidget { + const PushHomePage({super.key}); + + @override + State createState() => _PushHomePageState(); +} + +class _PushHomePageState extends State { + String _status = 'Ready'; + final List _log = []; + + void _updateStatus(String message) { + setState(() { + _status = message; + _log.add(message); + }); + debugPrint(message); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Ably Push Tutorial')), + body: const Center(child: Text('Wire up buttons in Step 4')), + ); + } +} +``` + + +## Step 2: Set up push notifications + +Push activation with `ably_flutter` registers the device with Ably and the underlying push service (FCM on Android, APNs on iOS). Add the following methods to your `_PushHomePageState` class: + + +```flutter +Future _requestPermissions() async { + if (Platform.isAndroid) { + final settings = await FirebaseMessaging.instance.requestPermission( + alert: true, + badge: true, + sound: true, + ); + _updateStatus('Permission: ${settings.authorizationStatus.name}'); + } else { + final granted = await AblyService().realtime.push.requestPermission(); + _updateStatus('Permission: ${granted ? 'authorized' : 'denied'}'); + } +} + +Future _activatePush() async { + _updateStatus('Activating push notifications...'); + try { + await AblyService().realtime.push.activate(); + final device = await AblyService().realtime.device(); + _updateStatus('Push activated. Device ID: ${device.id}'); + } catch (e) { + _updateStatus('Activation failed: $e'); + } +} + +Future _deactivatePush() async { + _updateStatus('Deactivating push notifications...'); + try { + await AblyService().realtime.push.deactivate(); + _updateStatus('Push deactivated'); + } catch (e) { + _updateStatus('Deactivation failed: $e'); + } +} +``` + + +## Step 3: Subscribe and test push notifications + +Push notifications delivered while your app is in the background or terminated are displayed as system notifications automatically. On iOS, Ably delivers pushes directly via APNs and foreground pushes are not received as Dart events. On Android, you can handle foreground FCM messages by listening to `FirebaseMessaging.onMessage` in the `initState` of your widget. Also call `_requestPermissions()` and `_subscribeToRealtime()` here so they run on startup: + + +```flutter +@override +void initState() { + super.initState(); + _requestPermissions(); + // Handle foreground FCM push notifications on Android + if (Platform.isAndroid) { + FirebaseMessaging.onMessage.listen((RemoteMessage message) { + final notification = message.notification; + if (notification != null) { + _updateStatus( + 'Push received: ${notification.title} — ${notification.body}', + ); + } + }); + } + // Subscribe to realtime messages + _subscribeToRealtime(); +} + +void _subscribeToRealtime() { + final channel = AblyService().realtime.channels.get(_channelName); + channel.subscribe().listen((ably.Message message) { + _updateStatus('Realtime message: ${message.name} — ${message.data}'); + }); +} +``` + + +To subscribe your device to a push channel so it receives channel-based push notifications, add the following methods: + + +```flutter +static const _channelName = 'exampleChannel1'; + +Future _subscribeToChannel() async { + try { + final channel = AblyService().realtime.channels.get(_channelName); + await channel.push.subscribeDevice(); + _updateStatus('Subscribed to push on channel: $_channelName'); + } catch (e) { + _updateStatus('Subscribe failed: $e'); + } +} + +Future _unsubscribeFromChannel() async { + try { + final channel = AblyService().realtime.channels.get(_channelName); + await channel.push.unsubscribeDevice(); + _updateStatus('Unsubscribed from channel: $_channelName'); + } catch (e) { + _updateStatus('Unsubscribe failed: $e'); + } +} +``` + + + + +Use the Ably CLI to test sending a push notification to your client ID: + + +```shell +ably push publish --client-id push-tutorial-client \ + --title "Test push" \ + --body "Hello from CLI!" \ + --data '{"foo":"bar","baz":"qux"}' +``` + + +Or send directly to a device ID: + + +```shell +ably push publish --device-id \ + --title "Test push" \ + --body "Hello from device ID!" +``` + + +To send push notifications via a channel, you first need a UI to subscribe to the channel. + +## Step 4: Build the UI + +Replace the `build` method of `_PushHomePageState` with the following to wire up all the controls: + + +```flutter +@override +Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Ably Push Tutorial')), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.all(12.0), + color: Colors.grey.shade100, + child: Text(_status, style: const TextStyle(fontSize: 14)), + ), + const SizedBox(height: 12), + ElevatedButton( + onPressed: _activatePush, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + foregroundColor: Colors.white), + child: const Text('Activate Push'), + ), + ElevatedButton( + onPressed: _deactivatePush, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + foregroundColor: Colors.white), + child: const Text('Deactivate Push'), + ), + ElevatedButton( + onPressed: _subscribeToChannel, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.purple, + foregroundColor: Colors.white), + child: const Text('Subscribe to Channel'), + ), + ElevatedButton( + onPressed: _unsubscribeFromChannel, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.orange, + foregroundColor: Colors.white), + child: const Text('Unsubscribe from Channel'), + ), + const SizedBox(height: 12), + Expanded( + child: Container( + color: Colors.white, + padding: const EdgeInsets.all(8.0), + child: ListView.builder( + itemCount: _log.length, + itemBuilder: (context, index) => Text( + _log[index], + style: const TextStyle( + fontFamily: 'monospace', fontSize: 12), + ), + ), + ), + ), + ], + ), + ), + ); +} +``` + + +Run your app on a real device or compatible emulator/simulator: + + +```shell +flutter run +``` + + +The app requests permissions automatically on startup. Tap **Activate Push** and wait until the status message displays your device ID. Try the CLI commands from Step 3 to send a test notification. + +### Send push via channel + +To test channel-based push, tap **Subscribe to Channel** in the app, then publish a message to `exampleChannel1` with a `push` `extras` field using the Ably CLI: + + +```shell +ably channels publish exampleChannel1 '{"name":"example","data":"Hello from CLI!","extras":{"push":{"notification":{"title":"Ably CLI","body":"Hello from CLI!"},"data":{"foo":"bar"}}}}' +``` + + +## Step 5: Send push with code + +The `ably_flutter` SDK does not implement the [Push Admin API](/docs/api/realtime-sdk/push-admin), so sending push directly to a `deviceId` or `clientId` from a Flutter client is not supported. Use the [Ably REST API](/docs/api/rest-sdk) from your backend server for direct push. + +From the Flutter app itself, send push by publishing a message to a channel with a `push` `extras` field. Any device subscribed to that channel (via Step 3) will receive the notification. Add the following method to `_PushHomePageState`: + + +```flutter +Future _sendPushToChannel() async { + try { + final channel = AblyService().realtime.channels.get(_channelName); + await channel.publish( + message: ably.Message( + name: 'example', + data: 'Hello from channel!', + extras: ably.MessageExtras({ + 'push': { + 'notification': { + 'title': 'Channel Push', + 'body': 'Push sent to $_channelName', + }, + 'data': {'foo': 'bar', 'baz': 'qux'}, + }, + }), + ), + ); + _updateStatus('Push sent to channel: $_channelName'); + } catch (e) { + _updateStatus('Failed to send push to channel: $e'); + } +} +``` + + +Add a button to the `children` list in the `build` method, before the `Expanded` log widget: + + +```flutter +ElevatedButton( + onPressed: _sendPushToChannel, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white), + child: const Text('Send Push to Channel'), +), +``` + + +Build and run the app again. Tap **Subscribe to Channel** first, then **Send Push to Channel** to send a push notification to all subscribed devices: + +![Screenshot of the Flutter push tutorial application](../../../../images/content/screenshots/getting-started/flutter-push-getting-started-guide.png) + +## Next steps + +* Understand [token authentication](/docs/auth/token) before going to production. +* Explore [push notification administration](/docs/push#push-admin) for managing devices and subscriptions. +* Learn about [channel rules](/docs/channels#rules) for channel-based push notifications. +* Read more about the [Push Admin API](/docs/api/realtime-sdk/push-admin). + +You can also explore the [Ably Flutter SDK](https://github.com/ably/ably-flutter) on GitHub, or visit the [API references](/docs/api/realtime-sdk?lang=flutter) for additional functionality.