# Getting started: Push Notifications in Kotlin This guide will get you started with Ably Push Notifications in a new Android application using Kotlin. You'll learn how to set up your application with Firebase Cloud Messaging (FCM), 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](https://ably.com/docs/channels.md#rules) for details. 5. Install [Android Studio](https://developer.android.com/studio). 6. Use a real Android device or an emulator with Google Play Services installed (required for FCM). ### (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 To enable push notifications, you need to configure FCM: 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 (`app/`). 4. In the Firebase Console, go to **Project Settings** → **Service accounts** and generate a new private key. Download the JSON file. 5. In the Ably dashboard, navigate to your app's **Notifications** tab. 6. Scroll to the **Push Notifications Setup** section and select **Configure Push**. 7. Follow the instructions to upload your Firebase service account JSON file. ### Create an Android project Create a new Android project in Android Studio with an **Empty Views Activity** template using Kotlin, and set the minimum SDK to API 26 (Android 8.0) or higher. Add the following to your **project-level** `build.gradle.kts`: #### Kotlin ``` plugins { id("com.google.gms.google-services") version "4.4.2" apply false } ``` Then add the following dependencies to your **app-level** `build.gradle.kts`: #### Kotlin ``` plugins { id("com.google.gms.google-services") } dependencies { // AppCompat for AppCompatActivity and Theme.AppCompat implementation("androidx.appcompat:appcompat:1.7.0") // Ably Android SDK (includes push notification support) implementation("io.ably:ably-android:1.2.+") // Firebase Cloud Messaging implementation(platform("com.google.firebase:firebase-bom:33.0.0")) implementation("com.google.firebase:firebase-messaging-ktx") // Required for LocalBroadcastManager implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0") } ``` Add the `INTERNET` and `POST_NOTIFICATIONS` permissions to your `AndroidManifest.xml`: #### Xml ``` ``` All further code can be added directly to your `MainActivity.kt`, `PushNotificationService.kt`, and `AblyHelper.kt` files. ## Step 1: Set up Ably Create an `AblyHelper.kt` file with a singleton object to manage your Ably Realtime client across the app: ### Kotlin ``` import android.content.Context import io.ably.lib.realtime.AblyRealtime import io.ably.lib.types.ClientOptions object AblyHelper { private var instance: AblyRealtime? = null fun getInstance(context: Context? = null): AblyRealtime { return instance ?: synchronized(this) { instance ?: run { val options = ClientOptions().apply { key = "your-api-key" // Use token authentication in production clientId = "push-tutorial-client" } AblyRealtime(options).also { if (context != null) it.setAndroidContext(context) instance = it } } } } } ``` Initialize the Ably client early in your app's lifecycle by creating an `Application` class. Create a new file `PushTutorialApp.kt`: ### Kotlin ``` import android.app.Application class PushTutorialApp : Application() { override fun onCreate() { super.onCreate() // Initialize Ably client on app startup AblyHelper.getInstance(this) } } ``` Register your `Application` class in `AndroidManifest.xml` inside the `` element: ### Xml ``` ``` ## Step 2: Set up push notifications To receive push notifications on Android, you need to integrate with FCM. Create a `PushNotificationService.kt` file that extends `FirebaseMessagingService`. This service handles new FCM tokens and incoming push messages: ### Kotlin ``` import android.app.NotificationChannel import android.app.NotificationManager import android.os.Build import android.util.Log import androidx.core.app.NotificationCompat import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage class PushNotificationService : FirebaseMessagingService() { companion object { private const val TAG = "PushService" private const val CHANNEL_ID = "push_tutorial_channel" } override fun onNewToken(token: String) { super.onNewToken(token) Log.d(TAG, "New FCM token received") // Re-activate push with Ably whenever the FCM token is refreshed try { AblyHelper.getInstance().push.activate() } catch (e: Exception) { Log.e(TAG, "Error updating FCM token with Ably", e) } } override fun onMessageReceived(remoteMessage: RemoteMessage) { super.onMessageReceived(remoteMessage) Log.d(TAG, "Message received from: ${remoteMessage.from}") // Display a notification when the app is in the foreground remoteMessage.notification?.let { notification -> showNotification( notification.title ?: "Push Notification", notification.body ?: "", remoteMessage.data ) } } private fun showNotification(title: String, body: String, data: Map) { val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( CHANNEL_ID, "Push Tutorial", NotificationManager.IMPORTANCE_DEFAULT ) notificationManager.createNotificationChannel(channel) } val notification = NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(android.R.drawable.ic_dialog_info) .setContentTitle(title) .setContentText(body) .setAutoCancel(true) .build() notificationManager.notify(System.currentTimeMillis().toInt(), notification) } } ``` Register the service in `AndroidManifest.xml` inside the `` element: ### Xml ``` ``` Now add push activation and deactivation to your `MainActivity.kt`. The Ably Android SDK activates push asynchronously and the result arrives via Android's broadcast system, so register a `BroadcastReceiver` to handle it (the following imports cover all steps in this guide): ### Kotlin ``` import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.Build import android.os.Bundle import android.util.Log import android.widget.Button import android.widget.ScrollView import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.localbroadcastmanager.content.LocalBroadcastManager import io.ably.lib.types.AblyException import io.ably.lib.types.ErrorInfo import io.ably.lib.realtime.CompletionListener import io.ably.lib.util.IntentUtils import com.google.gson.JsonObject import io.ably.lib.types.Message import io.ably.lib.types.MessageExtras import io.ably.lib.types.Param class MainActivity : AppCompatActivity() { companion object { private const val TAG = "MainActivity" private const val CHANNEL_NAME = "exampleChannel1" } private val realtime by lazy { AblyHelper.getInstance() } private val pushActivationReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when (intent.action) { "io.ably.broadcast.PUSH_ACTIVATE" -> { val error = IntentUtils.getErrorInfo(intent) if (error != null) { Log.e(TAG, "Push activation failed: ${error.message}") updateStatus("Push activation failed: ${error.message}") } else { try { val deviceId = realtime.device().id Log.d(TAG, "Push activated. Device ID: $deviceId") updateStatus("Push activated. Device ID: $deviceId") } catch (e: Exception) { Log.e(TAG, "Push activated but failed to get device ID: ${e.message}") updateStatus("Push activated") } } } "io.ably.broadcast.PUSH_DEACTIVATE" -> { val error = IntentUtils.getErrorInfo(intent) if (error != null) { Log.e(TAG, "Push deactivation failed: ${error.message}") } else { Log.d(TAG, "Push deactivated") updateStatus("Push notifications deactivated") } } } } } private fun updateStatus(message: String) { runOnUiThread { Log.d(TAG, message) // Wire this to your status TextView in Step 4 } } fun activatePushNotifications() { updateStatus("Activating push notifications...") try { realtime.push.activate() } catch (e: AblyException) { updateStatus("Activation error: ${e.message}") } } fun deactivatePushNotifications() { updateStatus("Deactivating push notifications...") try { realtime.push.deactivate() } catch (e: AblyException) { updateStatus("Deactivation error: ${e.message}") } } } ``` Your app is now configured to receive push notifications once activated. ## Step 3: Subscribe and test push notifications Push notifications delivered while your app is in the background are handled automatically by the FCM SDK and displayed as system notifications. For foreground handling, your `PushNotificationService.onMessageReceived()` method (from Step 2) displays a notification via `NotificationManager`. To subscribe your device to a channel so it can receive channel-based push notifications, add the following methods to `MainActivity`: ### Kotlin ``` fun subscribeToChannel(channelName: String) { val channel = realtime.channels.get(channelName) channel.push.subscribeDeviceAsync(object : CompletionListener { override fun onSuccess() { Log.d(TAG, "Subscribed to push on channel: $channelName") updateStatus("Subscribed to push on channel: $channelName") } override fun onError(reason: ErrorInfo?) { Log.e(TAG, "Failed to subscribe: ${reason?.message}") updateStatus("Failed to subscribe: ${reason?.message}") } }) } fun unsubscribeFromChannel(channelName: String) { val channel = realtime.channels.get(channelName) channel.push.unsubscribeDeviceAsync(object : CompletionListener { override fun onSuccess() { Log.d(TAG, "Unsubscribed from push on channel: $channelName") updateStatus("Unsubscribed from push on channel: $channelName") } override fun onError(reason: ErrorInfo?) { Log.e(TAG, "Failed to unsubscribe: ${reason?.message}") updateStatus("Failed to unsubscribe: ${reason?.message}") } }) } ``` Also add a realtime channel subscription to receive messages while the app is in the foreground: ### Kotlin ``` fun subscribeToRealtime(channelName: String) { val channel = realtime.channels.get(channelName) channel.subscribe { message -> Log.d(TAG, "Received message: ${message.name} - ${message.data}") } } ``` Now 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 Create the layout file `res/layout/activity_main.xml`: ### Xml ```