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
- Sign up for an Ably account.
- Create a new app, and create your first API key in the API Keys tab of the dashboard.
- Your API key needs the
publish,subscribecapabilities. - Also add the
push-admincapability if you're using the same API key to send a push notification. In production this would more likely be a server using a different API key.
- Add a rule to a channel so you can test sending push notification via a channel. Select Rules in the Ably dashboard, add a new rule and enable the Push notifications option.
- Install the Flutter SDK.
- 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 as an additional client to quickly test Pub/Sub features and push notifications.
- Install the Ably CLI:
npm install -g @ably/cli- Run the following to log in to your Ably account and set the default app and API key:
ably loginSet up Firebase Cloud Messaging (Android)
- Go to the Firebase Console and create a new project (or use an existing one).
- Add an Android app to your Firebase project using your application's package name.
- Download the
google-services.jsonfile and place it in your Android app module directory (android/app/). - In the Firebase Console, go to Project configuration → Service accounts and generate a new private key. Download the JSON file.
- In the Ably dashboard left sidebar, navigate to Push Notifications.
- Scroll to the Configure push service for devices section and press Configure Push.
- Upload your Firebase service account JSON file and press Save.
Set up APNs (iOS)
- In the Apple Developer portal, go to Certificates, Identifiers & Profiles → Keys.
- Add a new key and check Apple Push Notifications service (APNs), click Register.
- Download the
.p8file — you can only download it once. Note your Key ID and Team ID. - In the Ably dashboard left sidebar, navigate to Push Notifications.
- Scroll to the Configure push service for devices section and press Configure Push.
- Under Apple Push Notification Service, upload your
.p8file, 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:
flutter create ably_push_flutter --platforms android,ios
cd ably_push_flutterAdd the following dependencies to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
ably_flutter: ^1.2.35
firebase_core: ^3.0.0
firebase_messaging: ^15.0.0Then run:
flutter pub getConfigure Android
Add the Google Services plugin to android/build.gradle:
buildscript {
dependencies {
classpath 'com.google.gms:google-services:4.4.2'
}
}Apply the plugin at the top of android/app/build.gradle:
apply plugin: 'com.google.gms.google-services'Add the POST_NOTIFICATIONS permission to android/app/src/main/AndroidManifest.xml:
1
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />You can use the FlutterFire CLI to generate the firebase_options.dart file, which provides platform-specific Firebase configuration automatically. Run the following command in your project directory:
flutterfire configure --platforms=androidThe --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:
- Select the Runner target in Xcode.
- Go to the Signing & Capabilities tab.
- Click + Capability and add Push Notifications.
- 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 Pub/Sub client across the app:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 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<void> init() async {
final clientOptions = ably.ClientOptions(
key: 'demokey:*****', // 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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 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<PushHomePage> createState() => _PushHomePageState();
}
class _PushHomePageState extends State<PushHomePage> {
static const _channelName = 'my-first-push-channel';
String _status = 'Ready';
final List<String> _log = [];
void _updateStatus(String message) {
setState(() {
_status = message;
_log.add(message);
});
debugPrint(message);
}
@override
void initState() {
super.initState();
_subscribeToRealtime();
}
void _subscribeToRealtime() {
final channel = AblyService().realtime.channels.get(_channelName);
channel.subscribe().listen((ably.Message message) {
_updateStatus('Realtime message: ${message.name} — ${message.data}');
});
}
@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 3')),
);
}
}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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Future<void> _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<void> _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<void> _deactivatePush() async {
_updateStatus('Deactivating push notifications...');
try {
await AblyService().realtime.push.deactivate();
_updateStatus('Push deactivated');
} catch (e) {
_updateStatus('Deactivation failed: $e');
}
}Also update initState to call _requestPermissions() on startup:
1
2
3
4
5
6
@override
void initState() {
super.initState();
_requestPermissions();
_subscribeToRealtime();
}Handle push notifications
Push notifications delivered while the app is in the background are displayed as system notifications automatically. For foreground notifications on Android, listen to FirebaseMessaging.onMessage in initState:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@override
void initState() {
super.initState();
_requestPermissions();
if (Platform.isAndroid) {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
final notification = message.notification;
if (notification != null) {
_updateStatus(
'Push received: ${notification.title} — ${notification.body}',
);
}
});
}
_subscribeToRealtime();
}Step 3: Subscribe to channel push notifications
To subscribe your device to a push channel so it receives channel-based push notifications, add the following methods to _PushHomePageState:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Future<void> _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<void> _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');
}
}Step 4: Build the UI
Build a UI in your app to add buttons that call all push functions. Replace the build method of _PushHomePageState with the following to wire up all the controls:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@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:
flutter runStep 5: Publish a push notification
In the app tap Activate Push and wait until the status message displays your device ID.
Publish directly to your device
Publish a push notification directly to your client ID (or device ID using --device-id instead of --client-id) via the Ably CLI:
ably push publish --client-id push-tutorial-client \
--title "Hello" \
--body "World!" \
--data '{"foo":"bar"}'Publish via a channel
Tap Subscribe to Channel in the app, then publish a push notification to the channel using the Ably CLI:
ably push publish --channel my-first-push-channel \
--title "Hello" \
--body "World!" \
--message '{"name":"greeting","data":"Hello World!"}'If you tap Unsubscribe from Channel, the device no longer receives push notifications for that channel. Send the same command again and verify that no notification is received.
To see the full list of push publishing options with the Ably CLI, run ably push publish --help in your terminal or visit the Ably CLI push commands page. To send push notifications from your own server code instead of the CLI, see Push notification publishing.
Next steps
- Understand token authentication before going to production.
- Explore push notification administration for managing devices and subscriptions.
- Learn about channel rules for channel-based push notifications.
- Read more about the Push Admin API.
You can also explore the Ably Flutter SDK on GitHub, or visit the API references for additional functionality.