Migration Guide

Migration from Classic SDKs to Zendesk SDK

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

Only the versions of Classic SDK using Messaging 5.3.0 and onward are compatible with this migration.

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

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

The following steps will guide you, or your development team, 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.

You also have to set up the Zendesk side configuration, as documented here:

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. The snippets below demonstrate how to create a service for each of the SDK.

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

Snippet for the Zendesk SDK Push service:


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:


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.


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>

Migrating from version 1.x.x to 2.x.x

The Zendesk SDK version 2.x.x and above has been upgraded in order to enable us provide flexibility in what capabilities are included in your product when you integrate the SDK. There are some new and exciting capabilities being added which will enable you to engage with your customers better.

Due to the updates mentioned above, a migration is required for any integrations that are using any version of the SDK below 2.0.0.

SDK initialization moved from the Messaging module to the Zendesk module. See Initialize the SDK for more details. Initialization snippet now accepts a factory implementation of messaging, and errors are returned as a Throwable object instead of a ZendeskError object.

The following Messaging functions have also been migrated to Zendesk.

Old New
Messaging.initialize(...) Zendesk.initialize(...)
Messaging.instance().showMessaging() Zendesk.instance.messaging.showMessaging(...)

Auto migration

You might be able to automatically migrate your initialization snippet to the most recent version using Android Studio suggestions.

The Messaging functions have been deprecated in the latest version and will be removed in a future version on Android.