Starting with the Classic SDK update of Messaging 5.3.0, we improved the compatibility with Zendesk SDK to allow for easier migration from Classic SDK to Zendesk SDK.

Only Classic SDK versions that use Messaging 5.3.0 or later are compatible with this migration.

Note: messaging doesn't refer to the messaging channel. This is the name of the Classic SDK UI.

Some features require additional steps in order to function properly when using both the Classic SDK and Zendesk SDK.

Push notifications for Chat SDK and Zendesk SDK in the same app

The following steps show how to set up push notifications for both Chat SDK and Zendesk SDK in the same app.

Step 1 - Setting up push notifications in Firebase

Set up Firebase cloud messaging in your application so your application is capable of receiving push notifications from Firebase.

The Zendesk-side configuration must be completed as outlined in the following documentation:

Step 2 - Create a Firebase Service for both the Zendesk SDK and Chat SDK in your app

In order to receive push notifications for both SDKs at the same time, you must create a service for each SDK. For example:

Note: This implementation does not work with the default Zendesk SDK Push.

Snippet for the Zendesk SDK Push service:

ZendeskSDKFirebaseMessagingService

import com.google.firebase.messaging.FirebaseMessagingServiceimport com.google.firebase.messaging.RemoteMessageimport zendesk.logger.Loggerimport zendesk.messaging.android.push.PushNotificationsimport zendesk.messaging.android.push.PushResponsibility
class ZendeskSDKFirebaseMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(remoteMessage: RemoteMessage) {        Logger.d(LOG_TAG, "Received new FCM push: %s", remoteMessage.data)        when (PushNotifications.shouldBeDisplayed(remoteMessage.data)) {            PushResponsibility.MESSAGING_SHOULD_DISPLAY -> {                // This push belongs to Messaging and the SDK is able to display it to the end user                Logger.d(LOG_TAG, "onMessageReceived: MESSAGING_SHOULD_DISPLAY")                PushNotifications.displayNotification(context = this, messageData = remoteMessage.data)            }            PushResponsibility.MESSAGING_SHOULD_NOT_DISPLAY -> {                // This push belongs to Messaging but it should not be displayed to the end user                Logger.d(LOG_TAG, "onMessageReceived: MESSAGING_SHOULD_NOT_DISPLAY")            }            PushResponsibility.NOT_FROM_MESSAGING -> {                // This push does not belong to Messaging                Logger.d(LOG_TAG, "onMessageReceived: NOT_FROM_MESSAGING")            }        }    }
    override fun onNewToken(newToken: String) {        Logger.d(LOG_TAG, "Refreshed token: %s", newToken)        PushNotifications.updatePushNotificationToken(newToken)    }
    private companion object {        const val LOG_TAG = "ZendeskSDKFirebaseMessagingService"    }}

Snippet for the Classic Chat SDK Push service:

ChatSDKFirebaseMessagingService

import android.app.Notificationimport android.app.NotificationChannelimport android.app.NotificationManagerimport android.app.PendingIntentimport android.content.Contextimport android.os.Buildimport androidx.core.app.NotificationCompatimport com.google.firebase.messaging.FirebaseMessagingimport com.google.firebase.messaging.FirebaseMessagingServiceimport com.google.firebase.messaging.RemoteMessageimport com.zendesk.logger.Loggerimport com.zendesk.service.ErrorResponseimport com.zendesk.service.ZendeskCallbackimport zendesk.chat.Chatimport zendesk.chat.ChatConfigurationimport zendesk.chat.ChatEngineimport zendesk.chat.PushDataimport zendesk.classic.messaging.MessagingActivityimport zendesk.sample.sdksintegration.R
class ChatSDKFirebaseMessagingService : FirebaseMessagingService() {
    override fun onNewToken(token: String) {        Logger.d(LOG_TAG, "Refreshed token: %s", token)    }
    override fun onMessageReceived(remoteMessage: RemoteMessage) {        super.onMessageReceived(remoteMessage)
        Logger.d(LOG_TAG, "Message received: %s", remoteMessage.data)        handleChatPushData(remoteMessage)    }
    private fun handleChatPushData(remoteMessage: RemoteMessage) {        val pushNotificationsProvider = Chat.INSTANCE.providers()?.pushNotificationsProvider()
        if (pushNotificationsProvider == null) {            Logger.e(LOG_TAG, "Chat not initialized, unable to process received message")            return        }
        pushNotificationsProvider.processPushNotification(remoteMessage.data)?.let { pushData ->            when (pushData.type) {                PushData.Type.MESSAGE -> pushData.message?.let { showNotification(it) }                PushData.Type.END -> showNotification("Session has ended")            }        }    }
    private fun showNotification(contentText: String, contentTitle: String = SOURCE_CHAT) {        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager        val channelId = applicationContext.resources.getString(R.string.app_name)        val pendingIntent = setOpenNotificationIntent()
        createNotificationChannel(notificationManager, channelId)
        val notification = NotificationCompat.Builder(applicationContext, channelId)            .setSmallIcon(R.drawable.ic_expand_more)            .setDefaults(Notification.DEFAULT_ALL)            .setContentTitle(contentTitle)            .setContentText(contentText)            .setContentIntent(pendingIntent)            .setAutoCancel(true)            .build()
        notificationManager.notify(NOTIFICATION_ID, notification)    }
    /**     * Sets a [PendingIntent] to be triggered when the notification has been tapped.     *     * @return the the [MessagingActivity] intent if it exists, intent to lunch instead.     */    private fun setOpenNotificationIntent(): PendingIntent {        val chatConfiguration = ChatConfiguration.builder()            .withAgentAvailabilityEnabled(false)            .build()
        val messagingActivity = MessagingActivity.builder()            .withEngines(ChatEngine.engine())            .intent(this, chatConfiguration)
        val intentToLaunch = messagingActivity ?: packageManager.getLaunchIntentForPackage(packageName)
        val flags = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE        } else {            PendingIntent.FLAG_ONE_SHOT        }
        return PendingIntent.getActivity(            this,            INTENT_REQUEST_CODE,            intentToLaunch,            flags        )    }
    private fun createNotificationChannel(notificationManager: NotificationManager, channelId: String) {        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {            val name = getString(R.string.app_name)            val importance = NotificationManager.IMPORTANCE_HIGH            val channel = NotificationChannel(channelId, name, importance)
            notificationManager.createNotificationChannel(channel)        }    }
    companion object {        private const val LOG_TAG = "ChatSDKFirebaseMessagingService"        private const val NOTIFICATION_ID = 12345        private const val INTENT_REQUEST_CODE = 0        private const val SOURCE_CHAT = "Chat"
        fun init() {            FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->                if (task.isSuccessful) {                    task.result?.let {                        registerTokenForChat(it)                    }                } else {                    Logger.e(LOG_TAG, "Failed to retrieve FCM token")                }            }        }
        private fun registerTokenForChat(token: String) {            val pushNotificationsProvider = Chat.INSTANCE.providers()?.pushNotificationsProvider()
            if (pushNotificationsProvider == null) {                Logger.e(LOG_TAG, "Chat not initialized, unable to register token")                return            }
            pushNotificationsProvider.registerPushToken(                token, object : ZendeskCallback<Void?>() {                    override fun onSuccess(result: Void?) {                        Logger.d(LOG_TAG, "Chat Push Enabled: %s", token)                    }
                    override fun onError(error: ErrorResponse?) {                        Logger.e(LOG_TAG, "Chat Error Enable Push\nReason: ${error?.responseBody}")                    }                }            )        }    }}

Step 3 - Create a Firebase Proxy Service in your app

This is a helper class that allows registering both services ZendeskSDKFirebaseMessagingService and ChatSDKFirebaseMessagingService in your app, so each SDK is able to receive push notifications.

FirebaseMessagingProxyService

import android.app.Serviceimport com.google.firebase.messaging.FirebaseMessagingServiceimport com.google.firebase.messaging.RemoteMessageimport java.lang.reflect.Field
class FirebaseMessagingProxyService : FirebaseMessagingService() {
    private val messagingServices: List<FirebaseMessagingService> by lazy {        listOf(            ZendeskSDKFirebaseMessagingService(),            ChatSDKFirebaseMessagingService()        ).onEach { it.injectContext(this) }    }
    override fun onNewToken(token: String) {        super.onNewToken(token)        messagingServices.forEach { it.onNewToken(token) }    }
    override fun onMessageReceived(remoteMessage: RemoteMessage) {        super.onMessageReceived(remoteMessage)        messagingServices.forEach { it.onMessageReceived(remoteMessage) }    }
    override fun onDeletedMessages() {        super.onDeletedMessages()        messagingServices.forEach { it.onDeletedMessages() }    }
    override fun onMessageSent(message: String) {        super.onMessageSent(message)        messagingServices.forEach { it.onMessageSent(message) }    }
    override fun onSendError(message: String, e: Exception) {        super.onSendError(message, e)        messagingServices.forEach { it.onSendError(message, e) }    }}
private fun <T : Service> T.injectContext(context: T, func: T.() -> Unit = {}) {    setField("mBase", context)    func()}
private fun Class<*>.findDeclaredField(name: String): Field? {    var clazz: Class<*>? = this    do {        try {            return clazz?.getDeclaredField(name)        } catch (_: Throwable) {        }        clazz = clazz?.superclass    } while (clazz != null)    return null}
private fun Any.setField(name: String, value: Any): Boolean =    javaClass.findDeclaredField(name)?.let {        try {            it.isAccessible = true            it.set(this, value)            true        } catch (e: Throwable) {            false        }    } ?: false

Step 4 - Register the Firebase Proxy Service in your AndroidManifest.xml file

<application>    <!-- Use the Firebase proxy messaging service -->    <service        android:exported="false"        android:name=".pushnotification.FirebaseMessagingProxyService">        <intent-filter>            <action android:name="com.google.firebase.MESSAGING_EVENT" />        </intent-filter>    </service></application>