# 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, publish 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. * Your API key needs the `publish` and `subscribe` capabilities. * Also add the `push-admin` capability if you're using the same API key to publish a push notification. In production this would more likely be a server using a different API key. 3. Add a rule to a channel so you can test sending push notifications via a channel. Select [**Rules**](https://ably.com/accounts/any/apps/any/app_namespaces) in the Ably dashboard, add a new rule and enable the **Push notifications** option. 4. Install [Android Studio](https://developer.android.com/studio). 5. 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://ably.com/docs/platform/tools/cli.md?source=llms.txt) as an additional client to quickly test Pub/Sub features and push notifications. [`ably init`](https://ably.com/docs/cli/init.md?source=llms.txt) installs the Ably CLI, authenticates, and sets the default app and API key in a single command: #### Shell ``` npx -p @ably/cli ably init ``` ### 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 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 = "my-first-push-channel" } 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. ### Handle push notifications 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 import io.ably.lib.types.RegistrationToken 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") try { // Update the FCM token on Ably server AblyHelper.getInstance().push.getActivationContext().onNewRegistrationToken(RegistrationToken.Type.FCM, token) } 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 ``` ``` Push notifications delivered while your app is in the background are handled automatically by the FCM SDK and displayed as system notifications. The `onMessageReceived()` method handles foreground notifications via `NotificationManager`. ## Step 3: Subscribe to channel push notifications 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}") } } ``` ## Step 4: Build the UI Create the layout file `res/layout/activity_main.xml`: ### Xml ```