Notification setup
To change the behavior when a push notification is tapped (by default, ChatActivity is opened), use PendingIntentCreator. See Configuring the push notification tap action.
Overall flow
Push is delivered only when the SDK's WebSocket connection is inactive (the app is in the background or closed). The points where your app integrates: a FirebaseMessagingService / HmsMessageService subclass (receives push from the OS) and a call to chatCenterUI.handleFCMMessage(...) / handlePushMessage(...) (passes data to the SDK). Then the SDK decides — to update the message list (app in foreground) or to show a system notification (in background).
Permissions
The SDK already declares the required permissions in its AndroidManifest.xml (INTERNET, POST_NOTIFICATIONS, WAKE_LOCK, FOREGROUND_SERVICE, ACCESS_NETWORK_STATE). The Android Gradle Plugin's Manifest Merger correctly merges them with your app's manifest — you do not need to declare them again. The full list is in Android permissions.
POST_NOTIFICATIONS on Android 13+ (API 33)A manifest declaration alone does not show notifications. On Android 13+, request the permission via ActivityResultContracts.RequestPermission before the user expects their first push. Without an explicit grant, the system silently drops notifications — no SDK error.
// In an Activity
val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (!granted) {
// show a rationale or suggest enabling notifications in settings
}
}
// Before the first push subscription
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
Firebase
The same Firebase project whose google-services.json your app uses is configured by the edna Chat Center administrator in the channel on the server side: the Sender ID (from the Cloud Messaging settings) and the service-account.json (Firebase Console → Project settings → Service accounts → Generate new private key). If the values in the admin panel do not match the local google-services.json — a test push from Firebase Console arrives, but pushes from the edna backend are silently not delivered. Admin panel steps: Connecting the Android mobile chat.
In all the examples below, chatCenterUI is a field of your Application / Activity (the examples use the name MyApp — replace it with your class name; see Quick Start → Step 1).
ChatCenterUI.setFCMToken(...) / setHCMToken(...) can be called at any moment — the token is stored in Preferences and picked up by the SDK after initialization.
chatCenterUI.handleFCMMessage(...) / handlePushMessage(...) require initialization to be complete:
- With
init()(synchronous) — ensure thatApplication.onCreatehas finished before the first push arrives. - With
initAsync()— defer thehandleFCMMessagecall until theonInitCompletecallback, otherwise the push message will be lost. See Async initialization:initAsync().
Step 1. Create a FirebaseMessagingService
Create a FirebaseMessagingService subclass that forwards the token and messages to the SDK:
class CustomPushFcmIntentService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
ChatCenterUI.setFCMToken(token, applicationContext)
}
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
val chatCenterUI = (application as MyApp).chatCenterUI
chatCenterUI.handleFCMMessage(message.data)
}
}
handleFCMMessage(data) accepts a Map<String, String> and handles only push messages with the origin=threads flag — the rest are silently ignored. An alternative is handlePushMessage(bundle: Bundle) with the same filter.
message.dataThe payload structure is determined by the server-side push template setting in the edna Chat Center admin panel. The SDK processes standard fields (origin=threads, message identifiers, preview text) itself; additional keys from the template arrive as strings (nested objects are serialized to a JSON string by the admin panel before sending to Android). See Push notification template setup.
Coexisting with another FirebaseMessagingService
Android allows only one service with the com.google.firebase.MESSAGING_EVENT intent filter. If you already have your own service (for example, for Firebase Analytics or another push source), do not add a second one — add edna proxying inside the existing service:
override fun onNewToken(token: String) {
super.onNewToken(token)
// ... your existing code ...
ChatCenterUI.setFCMToken(token, applicationContext)
}
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
val chatCenterUI = (application as MyApp).chatCenterUI
if (message.data["origin"] == "threads") {
chatCenterUI.handleFCMMessage(message.data)
} else {
// your handler for non-edna push
}
}
Step 2. Add google-services.json
Two options are possible:
-
Using your Firebase account. Generate
google-services.jsonin the Firebase console. Pass two values from the same Firebase project to the edna Chat Center administrator:- Sender ID — from Firebase Console → Project settings → Cloud Messaging;
- service-account.json — from Firebase Console → Project settings → Service accounts → Generate new private key.
The administrator enters them into the channel on the "Notifications" tab — step-by-step instructions: Connecting the Android mobile chat.
-
Using edna's Firebase account. Provide your app's
applicationId— in return, you will receive agoogle-services.json.
Add google-services.json to the root of the app module (next to build.gradle). The module's applicationId must match the App_package field in the channel in the edna admin panel.
Step 3. Register the service in the manifest
<service android:name=".push.CustomPushFcmIntentService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
Notification channel (Android 8+)
The SDK creates the notification channel automatically on Android 8.0+. The channel priority is configured via ChatConfig.notificationImportance (default IMPORTANCE_DEFAULT). The field is marked @RequiresApi(N) — if your app's minSdk is below 24, wrap setting the value in a Build.VERSION.SDK_INT >= Build.VERSION_CODES.N check. The full value table and nuances are in Advanced → Setting the notification channel priority.
On logout(), the SDK clears the locally stored FCM/HCM token. In a multi-account scenario, after the next authorization, pass the token to the SDK again via ChatCenterUI.setFCMToken(...) / setHCMToken(...).
Huawei Mobile Services (HMS)
For Huawei devices without Google Play Services, use HMS Push Kit.
Before integration, make sure a separate "Mobile chat → Huawei" channel exists in edna Chat Center. The channel has App_package (= the applicationId of the Huawei build module), and on the "Notifications" tab — Huawei App ID and Huawei App Secret (these are the OAuth 2.0 Client ID and Client secret from AppGallery Connect → Project settings → App information, not the Push Kit App ID). Admin panel steps: Connecting the Huawei mobile chat.
Step 1. Add HMS dependencies
implementation 'com.huawei.hms:push:6.11.0.300'
Also add the com.huawei.agconnect plugin to build.gradle and the agconnect-services.json file to the app/ module.
agconnect-services.json— from AppGallery Connect → Project settings → App information → the agconnect-services.json section.- Huawei App ID and Huawei App Secret for the edna admin panel — in the same place, in the OAuth 2.0 Client ID section (Client ID and Client secret fields).
If the values in the edna admin panel are not updated after OAuth keys are re-created in AppGallery Connect, the backend stops authorizing in HMS Push Kit, and pushes are not delivered without an explicit error in logs. See Connecting the Huawei mobile chat.
Step 2. Create a push notification handler service
import android.os.Bundle
import android.util.Base64
import com.huawei.hms.push.HmsMessageService
import com.huawei.hms.push.RemoteMessage
import edna.chatcenter.ui.visual.core.ChatCenterUI
import org.json.JSONObject
class CustomPushHcmIntentService : HmsMessageService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
ChatCenterUI.setHCMToken(token, applicationContext)
}
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
val chatCenterUI = (application as MyApp).chatCenterUI
chatCenterUI.handlePushMessage(base64JsonStringToBundle(message.data))
}
private fun base64JsonStringToBundle(base64Str: String): Bundle = try {
val json = JSONObject(String(Base64.decode(base64Str, 0)))
Bundle().apply {
json.keys().forEach { key -> putString(key, json.getString(key)) }
}
} catch (_: Exception) {
Bundle()
}
}
HMS delivers the payload in RemoteMessage.data as a base64 string (with a JSON object inside), so before passing it to the SDK, you need to decode it into a Bundle and use handlePushMessage(bundle), not handleFCMMessage(map).
Step 3. Register the service in AndroidManifest.xml
<service android:name=".push.CustomPushHcmIntentService"
android:exported="false">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT" />
</intent-filter>
</service>
Step 4. Request the token during initialization
At app startup, request the HMS token:
import com.huawei.agconnect.AGConnectOptionsBuilder
import com.huawei.hms.aaid.HmsInstanceId
import com.huawei.hms.common.ApiException
fun requestHmsToken(context: Context) {
Thread {
try {
val appId = AGConnectOptionsBuilder().build(context).getString("client/app_id")
val token = HmsInstanceId.getInstance(context).getToken(appId, "HCM")
if (!token.isNullOrEmpty()) {
ChatCenterUI.setHCMToken(token, context)
}
} catch (e: ApiException) {
Log.e("HMS", "Token request failed", e)
}
}.start()
}
appId is taken from agconnect-services.json via AGConnectOptionsBuilder — no need to hardcode the value in code.
A complete HMS integration example is available in the demo app.
Verifying the integration
-
A test push arrives.
- FCM: Firebase Console → Cloud Messaging → Send test message, in the Additional options → Custom data section add the key
originwith valuethreads. - HMS: AppGallery Connect → Push Kit → Add notification → the Custom message section with the pair
origin=threads.
Without the
origin=threadspair, the SDK ignores the push. If in production you need to pass additional payload fields (e.g., theexternalIdof a message for a deep link), the setup is done on the server side via the push template: Push notification template setup. Custom fields are accessible frommessage.databefore callinghandleFCMMessage(...), or can be read by yourPendingIntentCreator. - FCM: Firebase Console → Cloud Messaging → Send test message, in the Additional options → Custom data section add the key
-
The notification is visible on the device (Android 13+). If the push arrives but the banner does not appear, check that the runtime
POST_NOTIFICATIONSpermission has been granted by the user (see the warning in the Permissions section). -
The initialization order is respected. If the first push after a cold start is lost,
ChatCenterUI.init(...)did not complete beforehandleFCMMessage(...)was called. See Async initialization.