Migrating from the Classic SDK to the Zendesk SDK
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.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import zendesk.logger.Logger
import zendesk.messaging.android.push.PushNotifications
import 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.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.os.Build
import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessaging
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.zendesk.logger.Logger
import com.zendesk.service.ErrorResponse
import com.zendesk.service.ZendeskCallback
import zendesk.chat.Chat
import zendesk.chat.ChatConfiguration
import zendesk.chat.ChatEngine
import zendesk.chat.PushData
import zendesk.classic.messaging.MessagingActivity
import 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.Service
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import 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>