SDK mobile sans interface utilisateur pour Android : sujets avancés, localisation et flux post-session

Ce document vous explique comment intégrer et personnaliser le SDK Headless Mobile dans votre application Android. Il aborde la localisation, les flux post-session et d'autres sujets avancés.

Flux post-session (VA post-session, enquêtes et CSAT)

Lorsque le chat en direct ou l'appel vocal se termine, les centres de contact souhaitent souvent recueillir des commentaires ou proposer des suivis automatisés. La CCAI Platform est compatible avec trois types d'expériences post-session :

  1. Agent virtuel (AV) post-session : chatbot automatisé qui poursuit la conversation une fois que l'agent humain s'est déconnecté.

  2. Enquêtes : formulaire à questions multiples adapté à la file d'attente spécifique.

  3. CSAT (satisfaction client) : note (par exemple, de 1 à 5 étoiles) et commentaires textuels facultatifs.

Appliquer la hiérarchie

Il est courant que les administrateurs configurent des règles qui se chevauchent dans le portail (par exemple, en activant la CSAT de manière globale, mais aussi en activant un agent virtuel post-session pour la file d'attente "Facturation").

Ce que le SDK fournit : un moteur de résolution des conflits strict. Le SDK évalue les règles du portail et s'assure qu'un seul flux post-session est déclenché par session, en respectant strictement la hiérarchie suivante :

  • Priorité après la session : VA après la session > Enquêtes > CSAT

    • Exemple : Si la CSAT et l'analyse post-session sont activées pour une file d'attente, l'utilisateur ne verra QUE l'analyse post-session.

Ce que vous allez créer : un écouteur qui attend la fin de la session, demande au SDK quel flux a remporté l'évaluation de la hiérarchie et affiche l'écran correspondant. Vous n'avez pas besoin d'écrire une logique if-else complexe pour déterminer quelle expérience est la plus efficace. Le SDK s'en charge pour vous.

Exemple de mise en œuvre

Cet extrait montre comment votre application doit écouter la fin d'une session et demander au SDK ce qu'il faut faire ensuite.

// Called when the active chat or call ends
fun handleSessionEnded(postSessionFlow: PostSessionFlow?) {
    // 1. Check if there is a required post-session flow.
    //    The SDK has already calculated the hierarchy (VA > Survey > CSAT).
    val flow = postSessionFlow
    if (flow == null) {
        // No feedback required. Safely close the support UI.
        closeSupportExperience()
        return
    }

    // 2. Route to the winning flow returned by the SDK.
    when (flow.type) {
        PostSessionType.VIRTUAL_AGENT -> transitionToPostSessionVA()
        PostSessionType.SURVEY -> showSurveyScreen(flow)
        PostSessionType.CSAT -> showCSATScreen(flow)
    }
}

Transition vers la phase post-session

Quel que soit le flux qui remporte la hiérarchie, vous devez appeler deux méthodes de cycle de vie partagées sur chatService pour signaler à la plate-forme CCAI Platform que la phase post-session a commencé.

Ce que fournit le SDK : méthodes de cycle de vie pour faire passer la session à la phase post-session et la faire progresser (startPostSession, readyPostSession).

Ce que vous allez créer : la logique d'orchestration qui appelle ces méthodes aux points corrects de votre flux d'interface utilisateur post-session.

Exemple de mise en œuvre

Ce code fait passer la session par les deux étapes du cycle de vie post-session.

lifecycleScope.launch {
    // Step 1: Start the post-session process (updates status to "in_progress")
    try {
        val postSessionResponse = CCAI.chatService?.startPostSession()
        if (postSessionResponse != null) {
            Log.d("CCAI", "Post-session started successfully: ${postSessionResponse.id}")
        }
    } catch (e: Exception) {
        Log.e("CCAI", "Failed to start post-session: ${e.message}")
    }

    // Step 2: Signal that post-session is ready (updates status to "ready")
    try {
        val readyResponse = CCAI.chatService?.readyPostSession()
        if (readyResponse != null) {
            Log.d("CCAI", "Post-session ready: ${readyResponse.id}")
        }
    } catch (e: Exception) {
        Log.e("CCAI", "Failed to prepare post-session: ${e.message}")
    }
}

Une fois que la plate-forme a confirmé la transition, passez à ce flux spécifique.

Champs d'état post-session sur ChatResponse

Deux champs d'orchestration post-session sur ChatResponse pilotent la logique de transition :

  • postSessionTransferStatus: String? : état actuel du transfert post-session (par exemple, "in_progress", "ready", "completed"). La plate-forme utilise ce champ comme clé pour la machine à états d'orchestration : startPostSession() le fait passer à "in_progress", readyPostSession() le fait passer à "ready", et vous observez cette valeur lors des émissions ChatResponse suivantes (à partir du flux de chat du service de chat) pour confirmer la transition côté plate-forme.

  • postSessionOptInRequired: Boolean? : indique si l'utilisateur doit activer explicitement le flux post-session avant son exécution. Lorsque true, affichez une invite d'activation dans votre UI et n'appelez startPostSession() qu'après que l'utilisateur a donné son consentement. Lorsque la valeur est false ou null, la vérification d'activation n'est pas requise et vous pouvez passer directement au flux post-session.

suspend fun orchestratePostSession(chat: ChatResponse) {
    // 1. Honor opt-in gating if required
    if (chat.postSessionOptInRequired == true) {
        val consented = presentPostSessionOptIn()
        if (!consented) {
            closeSupportExperience()
            return
        }
    }

    // 2. Drive the state machine
    CCAI.chatService?.startPostSession()   // → "in_progress"
    CCAI.chatService?.readyPostSession()   // → "ready"

    // 3. Observe postSessionTransferStatus on subsequent chat updates
    //    to confirm the transfer completed before rendering the winning flow.
}
  • postSessionRequired indique si un flux post-session est configuré pour la session.

  • postSessionOptInRequired indique si le consentement explicite de l'utilisateur est requis avant l'exécution.

  • postSessionTransferStatus vous indique où se trouve la plate-forme dans le cycle de vie de la transition.

Ces trois éléments sont indépendants. Vérifiez-les toujours tous.

Agent virtuel post-session (priorité la plus élevée)

Ce que fournit le SDK : l'état de transition pour transférer la session d'un agent humain vers un bot automatisé, et le routage des messages du bot.

Ce que vous allez faire : rien de complètement nouveau. Étant donné que l'AV post-session fonctionne comme un chat, vous pouvez laisser votre UI de chat personnalisée existante ouverte et la laisser gérer les messages automatisés entrants comme elle gérait les messages de l'agent humain.

Exemple de mise en œuvre

Si le SDK signale une transition vers une VA post-session, vous mettez à jour votre UI de chat pour refléter le changement.

fun transitionToPostSessionVA() {
    // Keep your chat UI open, but update the visual context
    myChatAdapter.addSystemMessage(
        "You are now speaking with our Virtual Assistant"
    )
    agentNameTextView.text = "Virtual Assistant"
    agentAvatarImageView.setImageResource(R.drawable.ic_bot_avatar)

    // The SDK will automatically start piping the VA's messages into
    // your standard chat message observer (messagesReceived).
}

Enquêtes (priorité moyenne)

Ce que fournit le SDK : rateService expose des méthodes permettant de récupérer les questions de l'enquête et d'envoyer les réponses pour une session de communication. Les questions de l'enquête sont configurées par l'administrateur pour chaque file d'attente et peuvent inclure plusieurs types de questions (CSAT, évaluation par étoiles, texte libre, énumération, échelle numérique).

Ce que vous allez créer : un formulaire dynamique qui récupère l'enquête, parcourt les questions, affiche l'UI appropriée (comme des boutons radio, des évaluations par étoiles ou des champs de texte) pour chacune d'elles, vérifie que les champs obligatoires sont remplis et regroupe les réponses pour les renvoyer.

Types de clés

// Survey response from getSurvey()
data class Survey(
    val id: Int?,
    val lang: String?,
    val questions: List<SurveyQuestion>?,
    val signOffText: String?  // "Thank you" message to show after submission
)

// Request payload for sendSurvey()
data class SurveyRequest(
    val answers: Map<Int, String>  // Maps question IDs to answer values
)

Exemple de mise en œuvre

Ce code récupère les questions de l'enquête à partir de rateService, les affiche dans un fragment personnalisé et envoie les réponses.

fun showSurveyScreen(communicationId: Long) {
    val rateService = CCAI.rateService ?: return

    lifecycleScope.launch {
        try {
            // 1. Fetch the survey questions for this session
            val survey = rateService.getSurvey(
                communicationId = communicationId,
                communicationType = "chats"
            ).getOrThrow()

            // 2. Present your custom survey UI
            val surveyFragment = MyCustomSurveyFragment.newInstance(
                survey.questions.orEmpty()
            )
            surveyFragment.onSubmit = { answersMap ->
                lifecycleScope.launch {
                    try {
                        // 3. Submit the answers
                        val request = SurveyRequest(answers = answersMap)
                        rateService.sendSurvey(
                            communicationId = communicationId,
                            communicationType = "chats",
                            answers = request
                        ).getOrThrow()
                        rateService.clear()
                        closeSupportExperience()
                    } catch (e: Exception) {
                        Log.e("CCAI", "Failed to submit survey: ${e.message}")
                    }
                }
            }
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, surveyFragment)
                .addToBackStack(null)
                .commit()
        } catch (e: Exception) {
            Log.e("CCAI", "Failed to load survey: ${e.message}")
            closeSupportExperience()
        }
    }
}

CSAT : satisfaction client (priorité la plus basse)

Ce que fournit le SDK : rateService expose une méthode rate() permettant d'envoyer un score CSAT et des commentaires facultatifs pour une session de communication. La note est généralement une valeur entière comprise entre 1 et 5.

Ce que vous allez créer : l'écran de notation visuelle (par exemple, une rangée d'étoiles sur lesquelles appuyer ou une échelle numérique de 1 à 5), un champ de texte si les commentaires sont autorisés et un bouton Envoyer.

Types de clés

// Request payload for rate()
data class RatingRequest(
    val rating: Int? = null,      // Typically 1-5
    val feedback: String? = null  // Optional qualitative feedback
)

Exemple de mise en œuvre

Une fois que l'utilisateur a appuyé sur une note et rédigé son commentaire, vous transmettez ces valeurs à rateService.

fun submitCSATFeedback(communicationId: Long, rating: Int, comment: String?) {
    val rateService = CCAI.rateService ?: return
    submitButton.isEnabled = false
    progressBar.isVisible = true

    lifecycleScope.launch {
        try {
            val request = RatingRequest(
                rating = rating,
                feedback = comment?.trim()?.takeIf { it.isNotEmpty() }
            )
            rateService.rate(
                communicationId = communicationId,
                communicationType = "chats",
                request = request
            ).getOrThrow()
            rateService.clear()
            showThankYouScreen()
        } catch (e: Exception) {
            showErrorToast("Failed to submit feedback. Please try again.")
        } finally {
            submitButton.isEnabled = true
            progressBar.isVisible = false
        }
    }
}

Télécharger des transcriptions de chat

Ce que fournit le SDK : une méthode pour télécharger la transcription du chat au format PDF.

Ce que vous allez créer : le bouton de téléchargement dans votre UI et la logique de gestion des fichiers (enregistrer, partager ou prévisualiser).

Exemple de mise en œuvre

Ce code montre comment télécharger une transcription Android, en récupérant le PDF sous forme de fichier, puis en l'enregistrant, en le partageant ou en l'affichant en aperçu dans votre application.

// Download to a file destination
val documentsDir = getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
val transcriptFile = File(documentsDir, "chat_transcript.pdf")

lifecycleScope.launch {
    try {
        val transcriptFile = CCAI.chatService?.downloadChatTranscript()
        if (transcriptFile != null) {
            Log.d("CCAI", "Transcript downloaded to: ${transcriptFile.absolutePath}")
        }
    } catch (e: Exception) {
        Log.e("CCAI", "Failed to download transcript: ${e.message}")
    }
}

La méthode downloadChatTranscript() d'Android n'accepte aucun paramètre et renvoie un File? pointant vers le PDF téléchargé. La méthode downloadChatTranscriptData() (qui renvoie les données PDF brutes directement en mémoire sans les enregistrer sur le disque) n'est disponible que sur iOS. Assurez-vous que votre application dispose des autorisations de stockage de fichiers appropriées si vous enregistrez des fichiers dans l'espace de stockage partagé.

Langues et localisation

Étant donné que vous créez une UI intégrée personnalisée à l'aide du SDK Headless Mobile, vous êtes responsable de la traduction des boutons, des menus et des alertes personnalisées de votre application.

Alors, pourquoi le SDK a-t-il besoin de connaître la langue de l'utilisateur ?

  1. Routage des files d'attente : CCAI Platform utilise le code de langue pour rediriger l'utilisateur vers un agent qui parle cette langue.

  2. Messages système : les messages automatiques générés par le backend de la plate-forme CCAI (par exemple, "Un agent va bientôt vous répondre") sont diffusés dans la langue active.

API de services linguistiques

Ce que fournit le SDK : une propriété languageCode en lecture et en écriture pour la langue active du SDK.

Ce que vous allez créer : une UI de outil de sélection de langue (si vous souhaitez que les utilisateurs choisissent manuellement) et la logique permettant de définir la langue avant le début d'une session.

// Read the active language
val currentLanguage = CCAI.languageService?.languageCode

// Set the active language before the user starts a support session
CCAI.languageService?.languageCode = "es"

Le SDK n'expose pas de liste fixe des langues acceptées. Si votre application affiche un outil de sélection de langue, gérez vous-même la liste d'affichage de l'application ou utilisez la configuration d'instance ou d'entreprise renvoyée par le SDK pour déterminer les langues à proposer.

Définir la langue du SDK (règle de pré-session)

Ce que fournit le SDK : un LanguageService qui compare la langue que vous demandez aux langues que vos administrateurs ont réellement activées dans le portail de la plate-forme CCAI.

Ce que vous devez créer : si vous souhaitez permettre aux utilisateurs de remplacer manuellement la langue (plutôt que de se fier uniquement aux paramètres par défaut de leur appareil), vous devez créer l'interface utilisateur de sélection (par exemple, un menu déroulant) et vous assurer qu'elle n'est accessible qu'avant qu'ils n'appuient sur un canal d'assistance.

Exemple de mise en œuvre

Ce code montre comment remplacer explicitement la langue du SDK à l'aide des codes de langue ISO 639 standards (par exemple, "es" pour l'espagnol et "fr" pour le français). La langue préférée est définie à l'aide du paramètre languageCode lors de l'initialisation du SDK dans la classe de données InitOptions. Vous pouvez également le définir lorsque vous réinitialisez le SDK :

// Set language during initialization
val initOptions = InitOptions(
    key = "YOUR_COMPANY_KEY",
    urlHost = "your_subdomain.ccaiplatform.com",
    languageCode = "es"  // for example, "es" for Spanish, "fr" for French
)

try {
    CCAI.initialize(context = this, options = initOptions)
} catch (e: Exception) {
    Log.e("CCAI", "Failed to initialize CCAI: ${e.message}")
}

Initialisation du module de chat (Android)

Si vous initialisez le module de chat à l'aide de la surcharge qui accepte un code de langue, vous pouvez également fournir la langue à cet endroit :

val chatOptions = ChatOptions(
    webFormInterface = WebFormManager(),
    downloadTranscriptVisibility = DownloadTranscriptVisibility.SHOW_ALL,
    greeting = "Hello! How can I help you?"
)
CCAI.initializeChat(context = this, options = chatOptions)

Vous pouvez également transmettre la langue lors de la récupération des menus de file d'attente :

val menuResult = CCAI.queueMenuService?.get(key = null)

Remplacez la valeur par session à l'aide de ChatRequest :

val chatRequest = ChatRequest(
    chat = Chat(
        menuId = menuId,
        languageCode = "fr"  // This session will use French
    )
)

Comment CCAI Platform détermine la langue finale

Si vous ne définissez pas explicitement de langue, le SDK a quand même besoin d'un code de langue pour le routage et les messages système. Pour que le comportement reste prévisible, il est préférable de le définir vous-même avant que l'utilisateur ne démarre une session d'assistance.

Utilisez cet ordre de priorité :

  1. Langue explicite (recommandé) : si vous définissez une langue dans InitOptions.languageCode, le SDK l'utilise.

  2. Mise à jour de la langue avant la session (toujours explicite) : si vous définissez CCAI.languageService?.languageCode = "es" avant que l'utilisateur n'entre dans une file d'attente, le SDK utilise cette langue mise à jour.

  3. Remplacement par session (chat uniquement) : si vous fournissez ChatRequest(chat = Chat(..., languageCode = "fr")), cette session de chat utilise le code de langue demandé.

  4. Si vous ne définissez rien : le comportement est défini par défaut sur la meilleure option disponible déterminée par le SDK en fonction de la configuration de l'appareil et de l'instance. Pour obtenir des résultats cohérents, évitez de vous y fier et définissez toujours une langue de manière explicite (étapes 1 à 3).

Gestion des erreurs, fallback et gestion des sessions

Même les applications les plus robustes rencontrent des problèmes de connexion, des dépendances manquantes ou des problèmes d'authentification. Le SDK mobile headless est conçu pour détecter ces problèmes de plate-forme sous-jacents et les faire remonter à votre application, ce qui vous permet de les gérer de manière appropriée dans votre UI personnalisée ou de vous appuyer sur les solutions de secours automatiques de CCAI Platform.

Gérer les erreurs du SDK

Ce que fournit le SDK : deux catégories d'exceptions typées, en plus des exceptions Kotlin standards :

  • HttpExceptionWithCode : générée par toute méthode de service asynchrone lorsqu'une requête HTTP vers la plate-forme CCAI échoue. Expose une propriété Int code pour vous permettre de créer des branches en fonction des codes d'état (par exemple, 401 pour l'authentification expirée, 429 pour la limitation du débit et 5xx pour les erreurs de serveur).

  • CallCreationError.AfterHours : erreur scellée générée par l'API scheduled-call (callService.createScheduledCall) en cas de conflit de créneau ou de fermeture de la période de planification. Contient un displayMessage localisé sur le serveur, destiné à être affiché directement à l'utilisateur.

  • Exceptions Kotlin ou Java standards : les échecs réseau sont signalés sous la forme java.io.IOException, l'annulation de coroutine sous la forme kotlinx.coroutines.CancellationException et tout événement inattendu sous la forme de la classe de base Exception.

Ce que vous allez créer : une instruction try/catch par couche autour de chaque appel de service SDK. Interceptez d'abord l'exception la plus spécifique (erreurs scellées spécifiques au domaine telles que CallCreationError.AfterHours le cas échéant, puis HttpExceptionWithCode pour la ramification de l'état HTTP), et revenez à un gestionnaire Exception générique pour les échecs réseau, système et imprévus.

Exemple de mise en œuvre

Ce code encapsule un appel SDK dans un modèle try/catch en couches, en capturant d'abord HttpExceptionWithCode pour créer une branche sur le code d'état HTTP, puis en revenant à un gestionnaire générique pour tout le reste.

import kotlinx.coroutines.launch
import androidx.lifecycle.lifecycleScope

lifecycleScope.launch {
    try {
        CCAI.chatService?.start(chatRequest)
        // Handle success - observe chat state using chatReceived
    } catch (e: HttpExceptionWithCode) {
        // Handle HTTP-level failures from CCAI endpoints - inspect e.code for status code
        Log.e("CCAI", "HTTP ${e.code}: ${e.message}")
        showErrorToast("Something went wrong. Please try again.")
    } catch (e: Exception) {
        // Handle other errors (examples: network and system)
        Log.e("CCAI", "Error: ${e.message}")
        showErrorToast("An unexpected error occurred.")
    }
}

Bonnes pratiques

  • Encapsulez chaque appel de service SDK dans un bloc try/catch en couches. Utilisez HttpExceptionWithCode pour créer des branches en fonction des codes d'état HTTP. Par exemple, redéclenchez ccaiShouldAuthenticate() sur 401 (jeton d'authentification expiré), appliquez un intervalle entre les tentatives sur 429 (limitation du débit) ou affichez une erreur générique sur les réponses 5xx.

  • Pour les API d'appels programmés, interceptez CallCreationError.AfterHours avant HttpExceptionWithCode afin que les conflits de créneaux 409 soient gérés avec le message localisé sur le serveur.

  • Enfin, récupérez le Exception générique comme filet de sécurité pour IOException, CancellationException et toute défaillance imprévue. En enregistrant les détails de l'exception pendant le développement, vous pouvez identifier rapidement les causes profondes.

Vérifier et effacer les sessions

Étant donné que le cycle de vie de votre application est distinct de celui de l'assistance CCAI Platform, vous devez gérer l'état de la session, en particulier en cas de déconnexion de l'utilisateur.

Ce que fournit le SDK : des API utilitaires pour vérifier si une session d'assistance est toujours en cours (y compris après le redémarrage ou l'arrêt forcé de l'application), et des méthodes pour effacer l'état d'authentification du SDK mis en cache et associé à un utilisateur final spécifique. Cela empêche le prochain utilisateur connecté d'hériter de la session de l'utilisateur précédent.

Ce que vous allez créer : des vérifications avant vol avant de tenter de démarrer de nouvelles sessions, et des routines de nettoyage qui se déclenchent lorsqu'un utilisateur se déconnecte de votre application principale.

Exemple d'implémentation (nettoyage de la déconnexion)

Met fin à toute session active, efface le jeton d'authentification mis en cache du SDK, efface les données de l'application locale et accède à l'écran de connexion. Il s'agit du nettoyage minimal requis pour empêcher l'héritage de session lors de la déconnexion de l'utilisateur.

fun handleUserLogout() {
    lifecycleScope.launch {
        // 1. Check for an in-progress session and end it cleanly
        try {
            val activeChat = CCAI.chatService?.getLastChatInProgress()
            if (activeChat != null) {
                CCAI.chatService?.endChat()
                Log.d("CCAI", "Ended active chat before logout.")
            }
        } catch (e: Exception) {
            // Session may not exist - that's fine
            Log.w("CCAI", "No active session to end: ${e.message}")
        }

        // 2. Clear the cached auth token
        //    (prevents the next user from inheriting the previous session)
        CCAI.authService?.updateAuthToken(null)

        // 3. Wipe your own app's user data
        MyAuthManager.clearLocalData()

        // 4. Navigate back to the login screen
        navigateToLoginScreen()
    }
}

Intégration des coroutines et de Flow Kotlin

Le SDK Headless Mobile pour Android est conçu autour des coroutines Kotlin et de Flow comme principaux modèles réactifs. Il s'agit de l'équivalent Android de l'intégration Combine du SDK iOS.

Ce que fournit le SDK : flux Kotlin et fonctions de suspension qui émettent des événements SDK en temps réel (messages entrants, modifications de l'état du partage d'écran) en tant que flux réactifs observables.

Ce que vous allez créer : des intégrations basées sur des coroutines qui collectent ces Flows et mettent à jour votre UI Android en conséquence.

Suspendre des fonctions

De nombreuses méthodes de service du SDK sont des fonctions de suspension Kotlin, ce qui signifie qu'elles peuvent être appelées à partir d'une portée de coroutine et qu'elles seront suspendues (et non bloquées) en attendant un résultat.

import kotlinx.coroutines.launch
import androidx.lifecycle.lifecycleScope

// In an Activity or Fragment
lifecycleScope.launch {
    try {
        // These are suspend functions - they suspend, not block
        val menuResult = CCAI.queueMenuService?.get()
        CCAI.chatService?.start(chatRequest)
        val company = CCAI.companyService?.get()
    } catch (e: Exception) {
        handleError(e)
    }
}

Flux du service de chat

chatService expose les propriétés SharedFlow Kotlin pour observer les événements de chat en temps réel. Collectez-les dans un champ d'application de coroutine tenant compte du cycle de vie (lifecycleScope ou viewModelScope).

Flow Émissions
messagesReceived Listes des messages de chat entrants (provenant d'agents, d'assistants virtuels ou du système).
typingEvent Lorsqu'un utilisateur ou un agent est en train de saisir du texte.
memberEvent Lorsqu'un utilisateur ou un agent rejoint ou quitte le chat (y compris en cas de transfert d'agent et de déconnexion).
stateChanged Changements d'état du fournisseur de chat (connexion, connecté, déconnecté, erreur).
chatReceived Données de réponse au chat lorsqu'un chat est reçu ou mis à jour (y compris les changements d'état, l'attribution d'un agent et l'état post-session).

chatService expose des Flows supplémentaires pour les événements de transfert (transferEventSubject), les résultats de vérification de l'état (checkStatusSubject) et les résultats d'envoi de messages (messageSendResultSubject).

Exemple de mise en œuvre

import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged

// 1. Incoming messages
lifecycleScope.launch {
    CCAI.chatService?.messagesReceived?.collect { messages ->
        for (message in messages) {
            renderIncomingMessage(message)
        }
    }
}

// 2. Typing indicators
lifecycleScope.launch {
    CCAI.chatService?.typingEvent?.collect { event ->
        when (event) {
            is ChatTypingEvent.Started -> showTypingIndicator()
            is ChatTypingEvent.Ended -> hideTypingIndicator()
        }
    }
}

// 3. Member join/leave
lifecycleScope.launch {
    CCAI.chatService?.memberEvent?.collect { event ->
        when (event) {
            is MemberEvent.Joined -> Log.d("Chat", "Member joined")
            is MemberEvent.Left -> Log.d("Chat", "Member left")
        }
        // Refresh chat status after member changes
        try { CCAI.chatService?.checkStatus() } catch (_: Exception) {}
    }
}

// 4. Chat provider state changes
lifecycleScope.launch {
    CCAI.chatService?.stateChanged?.distinctUntilChanged()?.collect { state ->
        // Update UI based on connection state (examples: connecting, connected)
        updateConnectionStatus(state)
    }
}

// 5. Chat metadata updates (agent assignment, status changes)
lifecycleScope.launch {
    CCAI.chatService?.chatReceived?.collect { chat ->
        Log.d("Chat", "Received chat update: ${chat.id}")
        updateAgentInfo(chat)
        handlePostSessionIfNeeded(chat)
    }
}

État du partage d'écran

Ce que fournit le SDK : l'état de la session de partage d'écran est fourni via le rappel onSessionStateChanged sur ScreenShareCallbacks, que vous transmettez à ScreenShareManager.startSession(). Pour en savoir plus, consultez SDK mobile sans interface utilisateur pour Android : voix, partage d'écran, appel programmé et e-mail. Le rappel se déclenche chaque fois que la session passe de INACTIVE à PENDING ou ACTIVE.

Ce que vous allez créer : un gestionnaire connecté à ScreenShareCallbacks.onSessionStateChanged qui met à jour votre UI lorsque la session passe d'un état à un autre, ainsi qu'une propriété locale (par exemple, currentScreenShareSessionState) si vous devez lire l'état actuel en dehors du rappel.

Exemple de mise en œuvre

En plus du chemin d'accès ScreenShareCallbacks, ScreenShareManager expose également des API addStateChangeListener / removeStateChangeListener autonomes et un accesseur getSessionState() synchrone. Utilisez-les lorsque vous devez observer l'état d'un composant autre que celui qui a appelé startSession(), ou pour effectuer une lecture unique non réactive. Contrairement au chemin d'accès ScreenShareCallbacks, où le rappel est libéré automatiquement à la fin de la session, les écouteurs autonomes ne sont pas libérés automatiquement. Appelez removeStateChangeListener(...) dans la suppression du propriétaire du cycle de vie pour éviter les fuites.

// Register a state change listener
val listener: (ScreenShareSessionState) -> Unit = { state ->
    when (state) {
        ScreenShareSessionState.INACTIVE -> Log.d("ScreenShare", "No active session")
        ScreenShareSessionState.PENDING -> Log.d("ScreenShare", "Session pending")
        ScreenShareSessionState.ACTIVE -> Log.d("ScreenShare", "Session active")
    }
}
ScreenShareManager.addStateChangeListener(listener)

// Get the current state directly (not reactive)
val currentState = ScreenShareManager.getSessionState()

// Clean up when done
ScreenShareManager.removeStateChangeListener(listener)

Données personnalisées

Ce que fournit le SDK : un mécanisme permettant d'associer des métadonnées structurées aux sessions de chat, soit sous forme de jetons JWT signés (vérifiés par votre backend), soit sous forme de charges utiles non signées (définies directement dans le client).

Ce que vous allez créer : la logique de collecte de données dans votre application et, pour les données signées, le point de terminaison de signature JWT sur votre serveur backend (consultez SDK mobile sans interface utilisateur pour Android : Premiers pas pour connaître l'intégralité du schéma d'authentification et de charge utile JWT).

Données personnalisées signées

Les données personnalisées signées utilisent un jeton JWT signé avec le code secret de votre entreprise sur votre serveur backend. Il s'agit de l'approche sécurisée pour transmettre des données sensibles ou fiables.

Exemple de mise en œuvre

Créez un objet de données personnalisé signé à partir d'un jeton JWT généré sur votre backend.

// JWT signed with company secret (signing must happen on your backend)
val customData = CustomData(signed = "header.payload.signature")

Données personnalisées non signées

Les données personnalisées non signées sont définies directement dans le client à l'aide de CustomDataPayload et CustomDataItem.

Exemple de mise en œuvre

Créez une charge utile non signée avec des éléments de données visibles et masqués par l'agent. CustomDataPayload prend un MutableMap<String, CustomDataItem?>, utilisez donc l'affectation d'indice (ou mutableMapOf) pour ajouter chaque élément par clé. La clé que vous choisissez (par exemple, "user_name" ou "account_tier") devient le nom du champ qui s'affiche pour l'agent, ainsi que pour le routage et les données analytiques.

val items = mutableMapOf<String, CustomDataItem?>()
items["user_name"] = CustomDataItem(
    label = "Name",
    value = "John"
)
items["user_age"] = CustomDataItem(
    label = "Age",
    value = 30,
    type = CustomDataType.NUMBER,
    invisibleToAgent = true
)
val payload = CustomDataPayload(items = items)
val customData = CustomData(unsigned = payload)

Les éléments de données personnalisés sont compatibles avec les propriétés suivantes :

  • label : libellé affiché pour l'agent.

  • value : valeur de données (exemples : String, Int, Bool).

  • type : type de données (STRING, NUMBER, DATE, URL, BOOLEAN).

  • invisibleToAgent : lorsque la valeur est true, les données sont masquées pour les agents, mais disponibles pour le routage et les analyses.

Utiliser des données personnalisées dans les demandes de chat

Joignez des données personnalisées lorsque vous démarrez une session de chat.

Exemple de mise en œuvre

val chatRequest = ChatRequest(
    chat = Chat(
        menuId = 123,
        languageCode = "en",
        customData = customData
    )
)

lifecycleScope.launch {
    try {
        CCAI.chatService?.start(chatRequest)
        // Chat started - observe chat state using chatReceived
    } catch (e: Exception) {
        Log.e("CCAI", "Failed to start chat: ${e.message}")
    }
}

Transfert de discussion externe

Ce que fournit le SDK : une structure de données (ExternalChatTransfer) permettant de transmettre le nom de l'agent précédent et le transcript de la conversation à une nouvelle session CCAI Platform.

Ce que vous allez créer : le flux de migration qui extrait le contexte de votre système de chat externe et le regroupe pour le SDK.

Exemple de mise en œuvre

Ce code crée un objet de transfert de chat externe avec le nom de l'agent précédent et l'associe aux données personnalisées de la session.

// ExternalChatAgent also accepts an optional `avatar: String? = null` parameter
// for displaying the previous agent's avatar, for example:
//     ExternalChatAgent(name = "John Smith", avatar = "https://cdn.example.com/avatars/jsmith.png")
val transfer = ExternalChatTransfer(
    agent = ExternalChatAgent(name = "John Smith"),
    transcript = emptyList()
)

// Attach to your custom data payload
val payload = CustomDataPayload()
payload.externalChatTransfer = transfer

val customData = CustomData(unsigned = payload)

Cela est utile lorsque vous migrez un utilisateur d'un système de chat tiers vers une session optimisée par CCAI Platform, en conservant le contexte de la conversation pour l'agent destinataire.

Remarques sur le threading

Le SDK mobile sans interface utilisateur pour Android est conçu pour fonctionner avec les coroutines Kotlin et est en grande partie sûr pour le thread principal. Toutefois, il existe des considérations importantes concernant le threading pour les intégrations sans interface utilisateur.

Sécurité du thread principal

Toutes les méthodes du SDK sont thread-safe et peuvent être appelées depuis n'importe quel thread. La plupart des appels de service du SDK (par exemple, chatService?.start(), queueMenuService?.get()) sont des fonctions de suspension qui gèrent leur propre threading en interne. Elles suspendent la coroutine appelante sans bloquer le thread. Vous pouvez les appeler en toute sécurité à partir du thread principal dans un champ d'application de coroutine :

// Safe to call from the main thread
lifecycleScope.launch {
    val menu = CCAI.queueMenuService?.get()  // Suspends, does not block
    updateUi(menu)  // Back on main thread
}

CCAIDelegate et threads en arrière-plan

La méthode ccaiShouldAuthenticate() de votre CCAIDelegate est une fonction de suspension. Vous pouvez donc effectuer des appels réseau en toute sécurité :

override suspend fun ccaiShouldAuthenticate(): String? {
    // This runs in a coroutine context - safe to call suspend functions
    return withContext(Dispatchers.IO) {
        MyBackendApi.getSignedJwt()  // Network call on IO dispatcher
    }
}

Mises à jour de l'UI à partir des rappels du SDK

Lorsque vous observez des événements SDK à l'aide de Flow, assurez-vous que les mises à jour de l'UI se produisent sur le thread principal. Si vous utilisez lifecycleScope, cette opération est effectuée automatiquement :

// lifecycleScope dispatches to Main by default
lifecycleScope.launch {
    chatMessagesFlow.collect { messages ->
        // Already on Main - safe to update UI
        chatAdapter.submitList(messages)
    }
}

Si vous collectez des données à partir d'un répartiteur non principal, revenez explicitement au répartiteur principal :

lifecycleScope.launch(Dispatchers.IO) {
    // Processing on IO thread
    val processed = heavyProcessing()
    withContext(Dispatchers.Main) {
        // Update UI on Main thread
        updateChatUi(processed)
    }
}

Threading d'initialisation

CCAI.initialize() et les appels d'initialisation de module associés, tels que initializeChat(), initializeCall() et initializeScreenShare(), doivent être appelés sur le thread principal de votre Application.onCreate(). Il s'agit d'appels synchrones qui configurent l'état interne du SDK.

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()  // Always on main thread

        // All initialization must happen on main thread
        CCAI.initialize(context = this, options = initOptions)
        CCAI.initializeChat(context = this, options = ChatOptions())
        CCAI.initializeCall(context = this)
    }
}

Gestion de la mémoire

Une gestion appropriée de la mémoire est essentielle pour éviter les fuites et assurer un nettoyage correct des ressources du SDK.

Cycle de vie des coroutines et des flux

Annulez toujours les références de coroutine Job et les collecteurs de flux dans les méthodes de cycle de vie Android (onDestroy() pour les activités, onCleared() pour les ViewModels). Si vous ne le faites pas, les références peuvent rester actives plus longtemps que prévu, ce qui peut entraîner des fuites de mémoire.

import kotlinx.coroutines.Job

class ChatViewModel : ViewModel() {
    private var chatJob: Job? = null

    fun observeChatMessages() {
        chatJob = viewModelScope.launch {
            CCAI.chatService?.messagesReceived?.collect { messages ->
                // Handle new messages
            }
        }
    }

    override fun onCleared() {
        super.onCleared()
        chatJob?.cancel()
    }
}

Références d'écouteur et de rappel

Utilisez des références faibles pour les écouteurs et les rappels afin d'éviter les cycles de rétention. Effacez tous les rappels enregistrés lorsque votre activité ou ViewModel est détruit.

class ChatActivity : AppCompatActivity() {
    private var chatJob: Job? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        chatJob = lifecycleScope.launch {
            CCAI.chatService?.messagesReceived?.collect { messages ->
                updateChatUI(messages)
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        chatJob?.cancel()
    }
}

Bonnes pratiques

  • Stockez les références de coroutine Job : stockez toujours les références Job renvoyées par launch {} pour éviter les coroutines incontrôlables qui survivent à leur portée.

  • Nettoyer dans les méthodes de cycle de vie : annulez tous les jobs et effacez les rappels dans onDestroy() (Activités) ou onCleared() (ViewModels).

  • Utilisez des références faibles pour les écouteurs : soyez prudent avec les lambdas qui capturent implicitement this (le contexte d'activité ou de fragment englobant), car cela peut empêcher la récupération de mémoire.

  • Les services sont gérés par le SDK : vous n'avez pas besoin de libérer manuellement les services du SDK. Ils sont gérés par l'objet singleton CCAI.

Dépannage

Cette section aborde les problèmes d'intégration courants et leurs solutions.

Problèmes de compilation et de dépendances

Cette section traite des problèmes courants liés à la compilation et aux dépendances.

Problème

Gradle ne parvient pas à résoudre com.ccaiplatform.android:CCAIKit.

  • Cause : Le dépôt Maven CCAI est manquant ou utilise la mauvaise URL.

  • Solution : assurez-vous que votre settings.gradle.kts inclut les éléments suivants :

    maven { url = uri("https://sdk.ujet.co/ccaip/android/") }
    

    Notez le segment de chemin d'accès /ccaip/. L'ancienne URL (https://sdk.ujet.co/android/) ne résoudra pas les artefacts.

Problème

Erreurs de compilation requises pour Java 17.

  • Cause : Ce SDK nécessite la compatibilité avec Java 17.

  • Solution : Mettez à jour votre build.gradle.kts :

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = "17"
    }
    

Problème

chatLibraryNotFound (code d'erreur 1100) lors de l'exécution.

  • Cause : Le module CCAIChatRed est manquant dans les dépendances.

  • Correction : CCAIChat et CCAIChatRed sont tous les deux requis pour le chat. Ajouter :

    implementation("com.ccaiplatform.android:CCAIChat:ccaiVersion")
    implementation("com.ccaiplatform.android:CCAIChatRed:ccaiVersion")
    

Problèmes d'initialisation

Cette section traite des problèmes d'initialisation courants.

Problème

NullPointerException lorsque vous accédez à CCAI.chatService.

  • Cause : CCAI.initializeChat() n'a pas été appelé ou a été appelé avant CCAI.initialize().

  • Correction : assurez-vous de l'ordre d'initialisation dans Application.onCreate() :

    1. CCAI.initialize(context, options = initOptions)

    2. CCAI.initializeChat(context, options = chatOptions)

    3. CCAI.initializeCall(context) (si vous utilisez les appels vocaux ou planifiés)

    4. CCAI.initializeScreenShare(context, options) (si vous utilisez le partage d'écran)

Problème

Les services SDK renvoient null après l'initialisation.

  • Cause : CCAI.initialize() n'a pas été appelé sur le thread principal ou la classe Application n'est pas enregistrée dans le fichier manifeste.

  • Corriger : vérifiez que la ligne android:name=".MainApplication" est présente dans votre fichier AndroidManifest.xml et que tous les appels init se trouvent dans onCreate().

Problèmes d'authentification

Cette section traite des problèmes d'authentification courants.

Problème

L'authentification échoue silencieusement ou ccaiShouldAuthenticate() n'est jamais appelé.

  • Cause : CCAIDelegate n'a pas été défini dans InitOptions.

  • Solution : assurez-vous que delegate est transmis à InitOptions :

    val initOptions = InitOptions(
        key = "YOUR_KEY",
        urlHost = "your_subdomain.ccaiplatform.com",
        delegate = MyCCAIDelegate()  // Must not be null
    )
    

Problème

Erreurs de validation JWT (code d'erreur 101)

  • Cause : Le jeton JWT a expiré, est incorrect ou a été signé avec le mauvais code secret.

  • Correction : vérifiez que votre backend :

    • Utilise le bon code secret de l'entreprise.

    • Définit des codes temporels iat et exp valides.

    • Renvoie une chaîne JWT correctement mise en forme.

Problèmes liés aux notifications push

Cette section traite des problèmes courants liés aux notifications push.

Problème

Les notifications push ne sont pas reçues ni traitées.

  • Cause : Le jeton FCM n'est pas enregistré auprès du SDK, le service de messagerie Firebase n'est pas enregistré ou les charges utiles de données entrantes ne sont pas transférées au SDK.

  • Solution :

    • Appelez CCAI.pushNotificationService?.updatePushToken(token) à la fois dans onNewToken() et au démarrage.

    • Transférez remoteMessage.data vers CCAI.pushNotificationService?.handlePushNotification(remoteMessage.data) depuis votre service de messagerie Firebase.

    • Vérifiez que google-services.json se trouve dans le module de votre application.

    • Vérifiez que la clé du compte de service Firebase est configurée dans le portail administrateur de la plate-forme CCAI.

    • Affichez toute notification visible ou UI d'appel entrant dans le code de votre propre application.

Problème

Le SDK ne réagit pas aux notifications push entrantes (les messages sont ignorés).

  • Cause : le type de données transmis à la méthode handlePushNotification() est incorrect.

  • Solution : Assurez-vous de transmettre remoteMessage.data (qui est un Map<String, String>), et non l'intégralité de l'objet RemoteMessage.

Problèmes liés aux sessions de chat

Cette section traite des problèmes courants liés aux sessions de chat.

Problème

chatService?.start() génère une exception ou renvoie null.

  • Cause : menuId non valide, échec de l'authentification ou problème de réseau.

  • Solution :

    • Vérifiez que menuId correspond à une file d'attente valide dans le portail d'administration.

    • Vérifiez que ccaiShouldAuthenticate() échange un jeton JWT signé valide via CCAI.authService?.authenticate(jwt) et renvoie le jeton d'authentification résultant.

    • Assurez-vous que l'appareil dispose d'une connectivité réseau.

    • Encapsulez l'appel dans un bloc try ou catch, puis enregistrez les détails de l'exception.

Problème

Les messages de chat ne s'affichent pas dans votre UI.

  • Cause : le flux de messages du service de chat n'est pas observé.

  • Correction : Assurez-vous de collecter les données du Flow du service de chat dans un champ d'application tenant compte du cycle de vie (voir la section Flux du service de chat ci-dessus).

Problèmes de partage d'écran

Cette section traite des problèmes courants liés au partage d'écran.

Problème

Le partage d'écran ne démarre pas après que l'utilisateur a donné son consentement.

  • Cause : le module CCAIScreenShare n'est pas initialisé ou Android MediaProjection n'est pas démarré.

  • Solution :

    • Assurez-vous que CCAI.initializeScreenShare() est appelé avec des ScreenShareOptions valides.

    • Vérifiez que vous démarrez correctement le MediaProjectionManager et le service de premier plan d'Android après avoir obtenu le consentement.

    • Sur Android 14 et versions ultérieures, assurez-vous que votre type de service de premier plan inclut mediaProjection.

Conseils de débogage généraux

  • Activez la journalisation détaillée pendant le développement pour afficher l'état interne du SDK.

  • Consultez le portail administrateur CCAI Platform pour vérifier la configuration des files d'attente, l'activation des canaux et les heures d'ouverture.

  • Surveillez le trafic réseau avec des outils tels que Charles Proxy ou l'inspecteur de réseau d'Android Studio pour vérifier les appels d'API vers *.ccaiplatform.com.

  • Vérifiez que les versions des modules correspondent : tous les modules com.ccaiplatform.android doivent utiliser le même numéro de version pour éviter les problèmes de compatibilité.

Conclusion de la série

Félicitations ! Vous avez terminé la série en cinq parties sur le SDK Headless Mobile pour Android. Votre intégration couvre désormais :

Pour vous y référer régulièrement, ajoutez Headless Mobile SDK - Android API Reference à vos favoris et consultez les notes de version de la plate-forme CCAI pour les mises à jour du SDK.