Questo documento mostra come integrare e personalizzare l'SDK mobile headless nella tua app per Android. Tratta argomenti avanzati come la localizzazione, i flussi post-sessione e altri.
Flussi post-sessione (VA post-sessione, sondaggi e CSAT)
Al termine di una chat live o di una chiamata vocale, i contact center spesso vogliono raccogliere feedback o offrire follow-up automatici. La piattaforma CCAI supporta tre tipi di esperienze post-sessione:
Agente virtuale post-sessione: un chatbot automatizzato che continua la conversazione dopo che l'agente umano si è disconnesso.
Sondaggi: un modulo con più domande personalizzato per la coda specifica.
CSAT (soddisfazione del cliente): una valutazione (ad esempio, da 1 a 5 stelle) e un feedback testuale facoltativo.
Applicazione della gerarchia
È normale che gli amministratori configurino regole sovrapposte nel portale (ad esempio, attivando la CSAT a livello globale, ma anche attivando un VA post-sessione per la coda Fatturazione).
Cosa fornisce l'SDK: un motore di risoluzione dei conflitti rigoroso. L'SDK valuta le regole del portale e garantisce che venga attivato un solo flusso post-sessione per sessione, rispettando rigorosamente la seguente gerarchia:
Priorità post-sessione: VA post-sessione > Sondaggi > CSAT
- Esempio: se sia il CSAT sia l'assistente virtuale post-sessione sono attivi per una coda, l'utente vedrà SOLO l'assistente virtuale post-sessione.
Cosa crei: un listener che attende la fine della sessione, chiede all'SDK quale flusso ha vinto la valutazione della gerarchia e disegna la schermata corrispondente. Non devi scrivere una logica if-else complessa per capire quale esperienza vince: l'SDK esegue i calcoli per te.
Esempio di implementazione
Questo snippet mostra come l'app deve ascoltare la fine di una sessione e chiedere all'SDK cosa fare dopo.
// 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)
}
}
Transizione alla fase post-sessione
Indipendentemente dal flusso che vince la gerarchia, devi chiamare due metodi del ciclo di vita condivisi su chatService per segnalare alla piattaforma CCAI Platform che è iniziata la fase post-sessione.
Cosa fornisce l'SDK: metodi del ciclo di vita per la transizione della sessione nella fase post-sessione e durante questa fase (startPostSession, readyPostSession).
Cosa crei: la logica di orchestrazione che chiama questi metodi nei punti corretti del flusso dell'interfaccia utente post-sessione.
Esempio di implementazione
Questo codice fa passare la sessione attraverso le due fasi del ciclo di vita post-sessione.
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}")
}
}
Dopo che la piattaforma ha riconosciuto la transizione, procedi con questo flusso specifico.
Campi dello stato post-sessione su ChatResponse
Due campi di orchestrazione post-sessione su ChatResponse guidano la logica di transizione:
postSessionTransferStatus: String?: lo stato attuale del trasferimento post-sessione (ad esempio"in_progress","ready","completed"). La piattaforma utilizza questo campo per la macchina a stati di orchestrazione:startPostSession()lo sposta su"in_progress",readyPostSession()lo sposta su"ready"e osservi questo valore nelle emissioniChatResponsesuccessive (dal flusso di chat del servizio di chat) per confermare la transizione lato piattaforma.postSessionOptInRequired: Boolean?: indica se l'utente deve attivare esplicitamente il flusso post-sessione prima che venga eseguito. Quandotrue, mostra un prompt di attivazione nella tua UI e chiamastartPostSession()solo dopo che l'utente ha dato il consenso. Quandofalseonull, il controllo di attivazione non è obbligatorio e puoi passare direttamente al flusso post-sessione.
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.
}
postSessionRequiredindica se è configurato qualsiasi flusso post-sessione per la sessione.postSessionOptInRequiredindica se è necessario il consenso esplicito dell'utente prima di eseguirlo.postSessionTransferStatusindica a che punto del ciclo di vita della transizione si trova la piattaforma.
Tutti e tre sono indipendenti, quindi controllali sempre.
Agente virtuale post-sessione (priorità più alta)
Cosa fornisce l'SDK: lo stato di transizione per spostare la sessione da un agente umano a un bot automatico e il routing dei messaggi del bot.
Cosa crei: niente di completamente nuovo. Poiché l'assistente virtuale post-sessione funziona come una chat, mantieni aperta l'interfaccia utente della chat personalizzata esistente e lascia che gestisca i messaggi automatizzati in arrivo proprio come ha gestito i messaggi dell'agente umano.
Esempio di implementazione
Se l'SDK segnala una transizione a un VA post-sessione, aggiorna la UI della chat per riflettere la modifica.
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).
}
Sondaggi (priorità media)
Cosa fornisce l'SDK: rateService espone metodi per recuperare le domande del sondaggio e inviare le risposte per una sessione di comunicazione. Le domande del sondaggio vengono configurate dall'amministratore per ogni coda e possono includere più tipi di domande (CSAT, valutazione a stelle, testo in formato libero, enumerazione, scala numerica).
Cosa crei: un modulo dinamico che recupera il sondaggio, scorre le domande, esegue il rendering dell'interfaccia utente appropriata (ad esempio pulsanti di opzione, valutazioni a stelle o campi di testo) per ciascuna, verifica che i campi obbligatori siano compilati e raggruppa le risposte da inviare.
Tipi di chiave
// 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
)
Esempio di implementazione
Questo codice recupera le domande del sondaggio da rateService, le visualizza in un frammento personalizzato e invia le risposte.
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 - soddisfazione del cliente (priorità più bassa)
Cosa fornisce l'SDK: l'rateService espone un metodo rate() per inviare un punteggio CSAT e un feedback facoltativo per una sessione di comunicazione. La valutazione è in genere un valore intero compreso tra 1 e 5.
Cosa crei:la schermata di valutazione visiva (ad esempio una riga di stelle toccabili o una scala numerica da 1 a 5), un campo di testo se è consentito il feedback e un pulsante Invia.
Tipi di chiave
// Request payload for rate()
data class RatingRequest(
val rating: Int? = null, // Typically 1-5
val feedback: String? = null // Optional qualitative feedback
)
Esempio di implementazione
Dopo che l'utente tocca una valutazione a stelle e scrive il suo feedback, trasmetti questi valori a 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
}
}
}
Download delle trascrizioni delle chat
Cosa fornisce l'SDK:un metodo per scaricare la trascrizione della chat come file PDF.
Cosa crei: il pulsante di download nell'interfaccia utente e la logica di gestione dei file (salvataggio, condivisione o anteprima).
Esempio di implementazione
Questo codice mostra l'approccio di download della trascrizione di Android: recupera il PDF come file e poi lo salva, lo condivide o ne visualizza l'anteprima nella tua app.
// 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}")
}
}
Il metodo Android downloadChatTranscript() non accetta parametri e restituisce un File? che punta al PDF scaricato. Il metodo downloadChatTranscriptData() (che restituisce i dati PDF non elaborati direttamente in memoria senza salvarli su disco) è disponibile solo su iOS. Se salvi i file nello spazio di archiviazione condiviso, assicurati che la tua app disponga delle autorizzazioni di archiviazione dei file appropriate.
Lingue e localizzazione
Poiché stai creando un'interfaccia utente integrata personalizzata utilizzando l'SDK Headless Mobile, la traduzione dei pulsanti, dei menu e degli avvisi personalizzati della tua app è di tua responsabilità.
Quindi, perché l'SDK deve conoscere la lingua dell'utente?
Routing delle code: la piattaforma CCAI utilizza il codice lingua per indirizzare l'utente a un agente che parla quella lingua.
Messaggi di sistema: i messaggi automatici generati dal backend della piattaforma CCAI (ad esempio "Un operatore sarà presto con te") vengono inviati nella lingua attiva.
API del servizio di traduzione
Cosa fornisce l'SDK: una proprietà languageCode di lettura e scrittura per la lingua SDK attiva.
Cosa crei: un'interfaccia utente per il selettore lingua (se vuoi che gli utenti scelgano manualmente) e la logica per impostare la lingua prima dell'inizio di una sessione.
// Read the active language
val currentLanguage = CCAI.languageService?.languageCode
// Set the active language before the user starts a support session
CCAI.languageService?.languageCode = "es"
L'SDK non espone un elenco fisso di lingue supportate. Se la tua app mostra un selettore lingua, gestisci autonomamente l'elenco di visualizzazione dell'app o utilizza la configurazione dell'istanza o dell'azienda restituita dall'SDK per determinare le lingue da offrire.
Impostazione della lingua dell'SDK (la regola pre-sessione)
Cosa fornisce l'SDK: un LanguageService che confronta la lingua che richiedi con le lingue effettivamente attivate dagli amministratori nel portale della piattaforma CCAI.
Cosa devi creare: se vuoi consentire agli utenti di eseguire l'override manuale della lingua (anziché fare affidamento solo sulle impostazioni predefinite del dispositivo), devi creare l'interfaccia utente di selezione (ad esempio un menu a discesa) e assicurarti che sia accessibile solo prima che tocchino un canale di assistenza.
Esempio di implementazione
Questo codice mostra come eseguire l'override esplicito della lingua dell'SDK utilizzando i codici lingua ISO 639 standard (ad esempio, "es" per lo spagnolo, "fr" per il francese). La lingua preferita viene impostata utilizzando il parametro languageCode durante l'inizializzazione dell'SDK nella classe di dati InitOptions. Puoi impostarlo anche durante la reinizializzazione dell'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}")
}
Inizializzazione del modulo di chat (Android)
Se inizializzi il modulo di chat utilizzando l'overload che accetta un codice lingua, puoi fornire la lingua anche lì:
val chatOptions = ChatOptions(
webFormInterface = WebFormManager(),
downloadTranscriptVisibility = DownloadTranscriptVisibility.SHOW_ALL,
greeting = "Hello! How can I help you?"
)
CCAI.initializeChat(context = this, options = chatOptions)
Puoi anche passare la lingua quando recuperi i menu della coda:
val menuResult = CCAI.queueMenuService?.get(key = null)
Esegui l'override per sessione utilizzando ChatRequest:
val chatRequest = ChatRequest(
chat = Chat(
menuId = menuId,
languageCode = "fr" // This session will use French
)
)
In che modo CCAI Platform risolve la lingua finale
Se non imposti esplicitamente una lingua, l'SDK ha comunque bisogno di un codice lingua per il routing e i messaggi di sistema. Per mantenere un comportamento prevedibile, è consigliabile impostarlo personalmente prima che l'utente avvii una sessione di assistenza.
Utilizza questo ordine di priorità:
Linguaggio esplicito (consigliato): se imposti una lingua in
InitOptions.languageCode, l'SDK la utilizza.Aggiornamento della lingua prima della sessione (ancora esplicito): se imposti
CCAI.languageService?.languageCode = "es"prima che l'utente entri in una coda, l'SDK utilizza la lingua aggiornata.Override per sessione (solo chat): se fornisci
ChatRequest(chat = Chat(..., languageCode = "fr")), la sessione di chat utilizza il codice lingua richiesto.Se non imposti nulla: il comportamento torna all'opzione migliore disponibile sul dispositivo e nella configurazione dell'istanza, come determinato dall'SDK. Per risultati coerenti, evita di fare affidamento su questa impostazione e imposta sempre una lingua in modo esplicito (passaggi 1-3).
Gestione degli errori, fallback e gestione delle sessioni
Anche le app più robuste riscontrano interruzioni delle connessioni, dipendenze mancanti o problemi di autenticazione. L'SDK mobile headless è progettato per rilevare questi problemi della piattaforma sottostanti e mostrarli nella tua app, consentendoti di gestirli in modo controllato all'interno della tua UI personalizzata o di fare affidamento ai fallback automatici di CCAI Platform.
Gestione degli errori SDK
Cosa fornisce l'SDK: due categorie di eccezioni tipizzate, oltre alle eccezioni Kotlin standard:
HttpExceptionWithCode: generato da qualsiasi metodo di servizio asincrono quando una richiesta HTTP alla piattaforma CCAI non va a buon fine. Espone una proprietàInt codein modo da poter creare rami in base ai codici di stato (ad esempio,401per l'autenticazione scaduta,429per limitazione di frequenza,5xxper gli errori del server).CallCreationError.AfterHours: errore sigillato generato dall'API scheduled-call (callService.createScheduledCall) quando si verifica un conflitto di slot o la finestra di programmazione è chiusa. Contiene undisplayMessagelocalizzato sul server e destinato alla visualizzazione diretta da parte dell'utente.Eccezioni Kotlin o Java standard: gli errori di rete vengono visualizzati come
java.io.IOException, l'annullamento della coroutine comekotlinx.coroutines.CancellationExceptione qualsiasi imprevisto come classeExceptiondi base.
Cosa crei: un blocco try o catch a più livelli intorno a ogni chiamata di servizio SDK. Rileva prima l'eccezione più specifica (errori sigillati specifici del dominio come CallCreationError.AfterHours, se applicabile, poi HttpExceptionWithCode per la ramificazione dello stato HTTP) e torna a un gestore Exception generico per errori di rete, di sistema e imprevisti.
Esempio di implementazione
Questo codice racchiude una chiamata SDK in un pattern try o catch a più livelli, rilevando prima HttpExceptionWithCode per ramificarsi in base al codice di stato HTTP, per poi ricorrere a un gestore generico per tutto il resto.
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.")
}
}
Best practice
Esegui il wrapping di ogni chiamata di servizio SDK in un blocco try/catch a più livelli. Utilizza
HttpExceptionWithCodeper creare rami in base ai codici di stato HTTP. Ad esempio, riattivaccaiShouldAuthenticate()su401(token di autenticazione scaduto), applica un backoff su429(limitazione di frequenza) o mostra un errore generico sulle risposte5xx.Per le API di chiamata pianificata, intercetta
CallCreationError.AfterHoursprimaHttpExceptionWithCodein modo che i conflitti di slot 409 vengano gestiti con il messaggio localizzato sul server.Infine, utilizza l'errore generico
Exceptioncome rete di sicurezza perIOException,CancellationExceptione qualsiasi errore imprevisto. La registrazione dei dettagli dell'eccezione durante lo sviluppo aiuta a individuare rapidamente le cause principali.
Controllare e cancellare le sessioni
Poiché il ciclo di vita della tua app è separato da quello del supporto della piattaforma CCAI, devi gestire lo stato della sessione, in particolare per quanto riguarda i logout degli utenti.
Cosa fornisce l'SDK: API di utilità per verificare se una sessione di assistenza è ancora in corso (anche dopo riavvii o chiusure forzate dell'app) e metodi per cancellare lo stato di autenticazione dell'SDK memorizzato nella cache e associato a un utente finale specifico, impedendo all'utente che esegue l'accesso successivo di ereditare la sessione dell'utente precedente.
Cosa crei: controlli pre-volo prima di tentare di avviare nuove sessioni e routine di pulizia che vengono attivate quando un utente esce dall'app principale.
Esempio di implementazione (pulizia al momento della disconnessione)
Termina qualsiasi sessione attiva, cancella il token di autenticazione memorizzato nella cache dall'SDK, cancella i dati locali dell'app e passa alla schermata di accesso. Si tratta della pulizia minima richiesta per impedire l'ereditarietà della sessione al momento della disconnessione dell'utente.
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()
}
}
Integrazione di coroutine e Flow di Kotlin
L'SDK Headless Mobile per Android è progettato intorno alle coroutine Kotlin e a Flow come pattern reattivi principali. Si tratta dell'equivalente Android dell'integrazione Combine dell'SDK per iOS.
Cosa fornisce l'SDK: flussi Kotlin Flow e funzioni di sospensione che emettono eventi SDK in tempo reale (messaggi in arrivo, modifiche dello stato di condivisione dello schermo) come flussi reattivi osservabili.
Cosa crei: integrazioni basate su coroutine che raccolgono questi flussi e aggiornano la tua UI Android di conseguenza.
Sospendi funzioni
Molti metodi di servizio SDK sono funzioni di sospensione Kotlin, il che significa che possono essere chiamati dall'interno di un ambito di coroutine e verranno sospesi (non bloccati) durante l'attesa di un risultato.
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)
}
}
Flussi del servizio di chat
chatService espone le proprietà SharedFlow Kotlin per osservare gli eventi della chat in tempo reale. Raccoglili all'interno di un ambito di coroutine consapevole del ciclo di vita (lifecycleScope o viewModelScope).
| Flow | Emette |
|---|---|
messagesReceived |
Elenchi di messaggi di chat in arrivo (da agenti, assistenti virtuali o dal sistema). |
typingEvent |
Quando un utente o un agente sta digitando. |
memberEvent |
Quando un utente o un agente entra o esce dalla chat (inclusi i trasferimenti e le disconnessioni degli agenti). |
stateChanged |
Modifiche dello stato del fornitore di chat (connessione, connesso, disconnesso, errore). |
chatReceived |
Dati delle risposte della chat quando una chat viene ricevuta o aggiornata (inclusi cambiamenti di stato, assegnazione dell'agente e stato post-sessione). |
chatService espone flussi aggiuntivi per eventi di trasferimento (transferEventSubject), risultati del controllo dello stato (checkStatusSubject) e risultati dell'invio di messaggi (messageSendResultSubject).
Esempio di implementazione
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)
}
}
Stato della condivisione schermo
Cosa fornisce l'SDK: lo stato della sessione di condivisione dello schermo viene fornito tramite il callback onSessionStateChanged su ScreenShareCallbacks, che passi a ScreenShareManager.startSession(). Consulta SDK mobile headless per Android: voce, condivisione dello schermo, chiamata pianificata ed email. Il callback viene attivato ogni volta che la sessione passa da INACTIVE, PENDING e ACTIVE.
Cosa crei: un gestore collegato a ScreenShareCallbacks.onSessionStateChanged che aggiorna la UI man mano che la sessione passa da uno stato all'altro, oltre a una proprietà locale (ad esempio currentScreenShareSessionState) se devi leggere lo stato attuale al di fuori del callback.
Esempio di implementazione
Oltre al percorso ScreenShareCallbacks, ScreenShareManager espone anche API addStateChangeListener / removeStateChangeListener autonome e un accessor getSessionState() sincrono. Utilizzali quando devi osservare lo stato di un componente diverso da quello che ha chiamato startSession() o per eseguire una lettura non reattiva una tantum. A differenza del percorso ScreenShareCallbacks, in cui il callback viene rilasciato automaticamente al termine della sessione, i listener autonomi non vengono rilasciati automaticamente. Chiama removeStateChangeListener(...) nello smontaggio del proprietario del ciclo di vita per evitare perdite.
// 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)
Dati personalizzati
Cosa fornisce l'SDK: un meccanismo per allegare metadati strutturati alle sessioni di chat, come JWT firmati (verificati dal backend) o come payload non firmati (impostati direttamente nel client).
Cosa crei: la logica di raccolta dei dati nella tua app e, per i dati firmati, l'endpoint di firma JWT sul tuo server di backend (consulta la sezione SDK Headless Mobile per Android: Guida introduttiva per lo schema completo di autenticazione e payload JWT).
Dati personalizzati firmati
I dati personalizzati firmati utilizzano un token JWT firmato con il segreto della tua azienda sul server di backend. Questo è l'approccio sicuro per il trasferimento di dati sensibili o attendibili.
Esempio di implementazione
Crea un oggetto dati personalizzato firmato da un JWT generato nel backend.
// JWT signed with company secret (signing must happen on your backend)
val customData = CustomData(signed = "header.payload.signature")
Dati personalizzati non firmati
I dati personalizzati non firmati vengono impostati direttamente nel client utilizzando CustomDataPayload e CustomDataItem.
Esempio di implementazione
Crea un payload non firmato con elementi di dati visibili e nascosti all'agente. CustomDataPayload accetta un MutableMap<String, CustomDataItem?>, quindi utilizza l'assegnazione di pedice (o mutableMapOf) per aggiungere ogni elemento per chiave. La chiave che scegli (ad esempio, "user_name" o "account_tier") diventa il nome del campo visualizzato dall'agente e per il routing/Analytics.
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)
Gli elementi di dati personalizzati supportano le seguenti proprietà:
label: l'etichetta da visualizzare mostrata all'agente.value: il valore dei dati (esempi:String,Int,Bool).type: tipo di dati (STRING,NUMBER,DATE,URL,BOOLEAN).invisibleToAgent: quandotrue, i dati sono nascosti agli agenti, ma disponibili per il routing e l'analisi.
Utilizzo di dati personalizzati nelle richieste di chat
Allega dati personalizzati all'avvio di una sessione di chat.
Esempio di implementazione
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}")
}
}
Trasferimento della chat esterna
Cosa fornisce l'SDK: una struttura di dati (ExternalChatTransfer) per trasferire il nome di un agente precedente e la trascrizione della conversazione in una nuova sessione di CCAI Platform.
Cosa crei: il flusso di migrazione che estrae il contesto dal sistema di chat esterno e lo pacchettizza per l'SDK.
Esempio di implementazione
Questo codice crea un oggetto di trasferimento della chat esterna con il nome dell'agente precedente e lo associa ai dati personalizzati della sessione.
// 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)
È utile quando si esegue la migrazione di un utente da un sistema di chat di terze parti a una sessione basata su CCAI Platform, mantenendo il contesto della conversazione per l'agente ricevente.
Considerazioni sui thread
L'SDK mobile headless per Android è progettato per funzionare con le coroutine Kotlin ed è in gran parte sicuro per il thread principale. Tuttavia, esistono importanti considerazioni sul threading per le integrazioni headless.
Sicurezza del thread principale
Tutti i metodi SDK sono thread-safe e possono essere chiamati da qualsiasi thread. La maggior parte delle chiamate di servizio SDK (ad esempio chatService?.start(), queueMenuService?.get()) sono funzioni di sospensione che gestiscono internamente i propri thread: sospendono la coroutine chiamante senza bloccare il thread. Puoi chiamarli in sicurezza dal thread principale all'interno di un ambito di 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 e thread in background
Il metodo ccaiShouldAuthenticate() in CCAIDelegate è una funzione di sospensione, quindi puoi eseguire in modo sicuro chiamate di rete al suo interno:
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
}
}
Aggiornamenti della UI dai callback dell'SDK
Quando osservi gli eventi SDK utilizzando Flow, assicurati che gli aggiornamenti dell'interfaccia utente avvengano sul thread principale. Se utilizzi lifecycleScope, questa operazione viene gestita automaticamente:
// lifecycleScope dispatches to Main by default
lifecycleScope.launch {
chatMessagesFlow.collect { messages ->
// Already on Main - safe to update UI
chatAdapter.submitList(messages)
}
}
Se raccogli da un dispatcher non principale, torna indietro in modo esplicito:
lifecycleScope.launch(Dispatchers.IO) {
// Processing on IO thread
val processed = heavyProcessing()
withContext(Dispatchers.Main) {
// Update UI on Main thread
updateChatUi(processed)
}
}
Threading di inizializzazione
CCAI.initialize() e le chiamate di inizializzazione dei moduli correlati, come initializeChat(), initializeCall() e initializeScreenShare(), devono essere chiamate nel thread principale del tuo Application.onCreate(). Si tratta di chiamate sincrone che configurano lo stato interno dell'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)
}
}
Gestione della memoria
Una corretta gestione della memoria è essenziale per evitare perdite e garantire la pulizia delle risorse dell'SDK.
Ciclo di vita di coroutine e Flow
Annulla sempre i riferimenti alle coroutine Job e i raccoglitori di flussi nei metodi del ciclo di vita di Android (onDestroy() per le attività, onCleared() per i ViewModel). In caso contrario, i riferimenti possono rimanere attivi più a lungo del previsto, causando perdite di memoria.
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()
}
}
Riferimenti a listener e callback
Utilizza riferimenti deboli per i listener e i callback per evitare cicli di conservazione. Cancella tutti i callback registrati quando l'attività o il ViewModel viene eliminato.
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()
}
}
Best practice
Memorizza i riferimenti alle coroutine
Job: memorizza sempre i riferimentiJobrestituiti dalaunch {}per evitare coroutine incontrollate che sopravvivono al loro ambito.Liberare spazio nei metodi del ciclo di vita: annulla tutti i job e cancella i callback in
onDestroy()(attività) oonCleared()(ViewModel).Utilizza riferimenti deboli per i listener: fai attenzione alle espressioni lambda che acquisiscono implicitamente
this(il contesto di Activity o Fragment contenitore), in quanto ciò può impedire la garbage collection.I servizi sono gestiti dall'SDK: non devi rilasciare manualmente i servizi SDK, che vengono gestiti dall'oggetto singleton
CCAI.
Risoluzione dei problemi
Questa sezione tratta i problemi di integrazione comuni e le relative soluzioni.
Problemi di build e dipendenze
Questa sezione tratta i problemi comuni di build e dipendenze.
Problema
Gradle non riesce a risolvere com.ccaiplatform.android:CCAIKit.
Causa: il repository Maven di CCAI non è presente o utilizza l'URL errato.
Correzione: assicurati che il tuo
settings.gradle.ktsincluda:maven { url = uri("https://sdk.ujet.co/ccaip/android/") }Prendi nota del segmento di percorso
/ccaip/. L'URL legacy (https://sdk.ujet.co/android/) non risolve gli artefatti.
Problema
Errori di compilazione richiesti da Java 17.
Causa: questo SDK richiede la compatibilità con Java 17.
Correzione: aggiorna il tuo
build.gradle.kts:compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { jvmTarget = "17" }
Problema
chatLibraryNotFound (codice di errore 1100) durante il runtime.
Causa: il modulo
CCAIChatRednon è presente nelle dipendenze.Correzione: sia
CCAIChatcheCCAIChatRedsono necessari per la chat. Aggiungi:implementation("com.ccaiplatform.android:CCAIChat:ccaiVersion") implementation("com.ccaiplatform.android:CCAIChatRed:ccaiVersion")
Problemi di inizializzazione
Questa sezione tratta i problemi di inizializzazione più comuni.
Problema
NullPointerException quando accedi a CCAI.chatService.
Causa:
CCAI.initializeChat()non è stato chiamato o è stato chiamato prima diCCAI.initialize().Correzione: assicurati che l'ordine di inizializzazione in
Application.onCreate()sia corretto:CCAI.initialize(context, options = initOptions)CCAI.initializeChat(context, options = chatOptions)CCAI.initializeCall(context)(se utilizzi le chiamate vocali o programmate)CCAI.initializeScreenShare(context, options)(se utilizzi la condivisione schermo)
Problema
I servizi SDK restituiscono null dopo l'inizializzazione.
Causa:
CCAI.initialize()non è stato chiamato nel thread principale oppure la classeApplicationnon è registrata nel manifest.Correzione: verifica che la riga
android:name=".MainApplication"sia presente nel fileAndroidManifest.xmle che tutte le chiamate init siano inonCreate().
Problemi di autenticazione
Questa sezione tratta i problemi di autenticazione comuni.
Problema
L'autenticazione non riesce in modo silenzioso o ccaiShouldAuthenticate() non viene mai chiamato.
Causa:
CCAIDelegatenon è stato impostato inInitOptions.Correzione: assicurati che
delegatevenga passato aInitOptions:val initOptions = InitOptions( key = "YOUR_KEY", urlHost = "your_subdomain.ccaiplatform.com", delegate = MyCCAIDelegate() // Must not be null )
Problema
Errori di convalida JWT (codice di errore 101).
Causa: il JWT è scaduto, non è valido o è firmato con il secret errato.
Correzione: verifica che il backend:
Utilizza il codice segreto dell'azienda corretto.
Imposta timestamp
iateexpvalidi.Restituisce una stringa JWT formattata correttamente.
Problemi relativi alle notifiche push
Questa sezione tratta i problemi comuni relativi alle notifiche push.
Problema
Le notifiche push non vengono ricevute o gestite.
Causa: il token FCM non è registrato con l'SDK, il servizio di messaggistica Firebase non è registrato o i payload di dati in entrata non vengono inoltrati all'SDK.
Correzione:
Chiama
CCAI.pushNotificationService?.updatePushToken(token)sia inonNewToken()sia all'avvio.Inoltra
remoteMessage.dataaCCAI.pushNotificationService?.handlePushNotification(remoteMessage.data)dal servizio di messaggistica Firebase.Verifica che
google-services.jsonsia nel modulo dell'app.Verifica che la chiave del service account Firebase sia configurata nel portale di amministrazione della piattaforma CCAI.
Esegui il rendering di qualsiasi notifica visibile o UI di chiamata in arrivo nel codice della tua app.
Problema
L'SDK non reagisce alle notifiche push in arrivo (i messaggi vengono ignorati).
Causa: passaggio del tipo di dati errato al metodo
handlePushNotification().Correzione: assicurati di passare
remoteMessage.data(che è unMap<String, String>), non l'intero oggettoRemoteMessage.
Problemi con la sessione di chat
Questa sezione tratta i problemi comuni relativi alle sessioni di chat.
Problema
chatService?.start() genera un'eccezione o restituisce null.
Causa:
menuIdnon valido, autenticazione non riuscita o problema di rete.Correzione:
Verifica che
menuIdcorrisponda a una coda valida nel portale amministratore.Verifica che
ccaiShouldAuthenticate()scambi un JWT firmato valido tramiteCCAI.authService?.authenticate(jwt)e restituisca il token di autenticazione risultante.Assicurati che il dispositivo abbia una connettività di rete.
Racchiudi la chiamata in un blocco try o catch, poi registra i dettagli dell'eccezione.
Problema
I messaggi di chat non vengono visualizzati nella UI.
Causa: mancata osservanza del flusso di messaggi del servizio di chat.
Correzione: assicurati di raccogliere i dati dal flusso del servizio di chat in un ambito sensibile al ciclo di vita (vedi la sezione Flussi del servizio di chat sopra).
Problemi di condivisione schermo
Questa sezione tratta i problemi comuni di condivisione dello schermo.
Problema
La condivisione dello schermo non viene avviata dopo che l'utente ha dato il consenso.
Causa: modulo
CCAIScreenSharenon inizializzato o AndroidMediaProjectionnon avviato.Correzione:
Assicurati che
CCAI.initializeScreenShare()venga chiamato conScreenShareOptionsvalidi.Verifica di avviare correttamente
MediaProjectionManagere il servizio in primo piano di Android dopo il consenso.Su Android 14 e versioni successive, assicurati che il tipo di servizio in primo piano includa
mediaProjection.
Suggerimenti generali per il debug
Attiva il logging dettagliato durante lo sviluppo per visualizzare lo stato interno dell'SDK.
Controlla il portale amministratore della piattaforma CCAI per verificare la configurazione delle code, l'attivazione dei canali e l'orario di apertura.
Monitora il traffico di rete con strumenti come Charles Proxy o Network Inspector di Android Studio per verificare le chiamate API a
*.ccaiplatform.com.Verifica che le versioni dei moduli corrispondano: tutti i moduli
com.ccaiplatform.androiddevono utilizzare lo stesso numero di versione per evitare problemi di compatibilità.
Tempo di wrap up della serie
Congratulazioni, hai completato la serie di cinque parti sull'SDK mobile headless per Android. La tua integrazione ora copre:
Configurazione e inizializzazione (SDK Headless Mobile per Android: inizia)
Code, canali e chat (SDK headless per dispositivi mobili per Android: code, canali e chat)
Voce, condivisione schermo, chiamata pianificata ed email (SDK mobile headless per Android: voce, condivisione schermo, chiamata pianificata ed email)
Azioni intelligenti, allegati e deviazione (SDK mobile headless per Android: azioni intelligenti, allegati e deviazione)
Flussi post-sessione, localizzazione, gestione degli errori e argomenti avanzati (questo articolo)
Per un riferimento continuo, aggiungi ai preferiti la documentazione di riferimento dell'API Headless Mobile SDK - Android e tieni d'occhio le note di rilascio della piattaforma CCAI per gli aggiornamenti dell'SDK.