Dokumen ini menunjukkan cara mengintegrasikan dan menyesuaikan Headless Mobile SDK di aplikasi Android Anda. Bagian ini mencakup pelokalan, alur pasca-sesi, dan topik lanjutan lainnya.
Alur pasca-sesi (VA pasca-sesi, survei, dan CSAT)
Saat live chat atau panggilan suara berakhir, pusat kontak sering kali ingin mengumpulkan masukan atau menawarkan tindak lanjut otomatis. Platform CCAI mendukung tiga jenis pengalaman pasca-sesi:
Agen virtual (VA) pasca-sesi: Chatbot otomatis yang melanjutkan percakapan setelah agen manusia mengakhiri percakapan.
Survei: Formulir multi-pertanyaan yang disesuaikan dengan antrean tertentu.
CSAT (kepuasan pelanggan): Rating (misalnya, 1–5 bintang) dan masukan teks opsional.
Menerapkan hierarki
Admin biasanya mengonfigurasi aturan yang tumpang-tindih di portal (misalnya, mengaktifkan CSAT secara global, tetapi juga mengaktifkan VA pasca-sesi untuk antrean Penagihan).
Yang disediakan SDK: Mesin penyelesaian konflik yang ketat. SDK mengevaluasi aturan portal dan memastikan bahwa hanya satu alur pasca-sesi yang dipicu per sesi, dengan mematuhi hierarki berikut:
Prioritas setelah sesi: VA setelah sesi > Survei > CSAT
- Contoh: Jika CSAT dan VA pasca-sesi diaktifkan untuk antrean, pengguna HANYA akan melihat VA pasca-sesi.
Yang Anda bangun: Pemroses yang menunggu sesi berakhir, menanyakan kepada SDK alur mana yang memenangkan evaluasi hierarki, dan menggambar layar yang sesuai. Anda tidak perlu menulis logika if-else yang rumit untuk mengetahui pengalaman mana yang menang; SDK akan melakukan perhitungan tersebut untuk Anda.
Contoh penerapan
Cuplikan ini menunjukkan cara aplikasi Anda harus memproses akhir sesi dan meminta SDK untuk menentukan tindakan selanjutnya.
// 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)
}
}
Beralih ke fase setelah sesi
Terlepas dari alur mana yang memenangkan hierarki, Anda harus memanggil dua metode siklus proses bersama di chatService untuk memberi sinyal kepada platform CCAI Platform bahwa fase pasca-sesi telah dimulai.
Yang disediakan SDK: Metode siklus proses untuk mentransisikan sesi ke dalam dan melalui fase pasca-sesi (startPostSession, readyPostSession).
Yang Anda bangun: Logika orkestrasi yang memanggil metode ini pada titik yang tepat dalam alur UI pasca-sesi Anda.
Contoh penerapan
Kode ini mengalihkan sesi melalui dua tahap siklus proses pasca-sesi.
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}")
}
}
Setelah platform mengonfirmasi transisi, lanjutkan ke alur khusus ini.
Kolom status pasca-sesi di ChatResponse
Dua kolom orkestrasi pasca-sesi di ChatResponse mendorong logika transisi:
postSessionTransferStatus: String?- Status saat ini dari transfer pasca-sesi (misalnya,"in_progress","ready","completed"). Platform mengindeks mesin status orkestrasi dari kolom ini:startPostSession()memindahkannya ke"in_progress",readyPostSession()memindahkannya ke"ready", dan Anda mengamati nilai ini pada emisiChatResponseberikutnya (dari alur percakapan layanan chat) untuk mengonfirmasi transisi sisi platform.postSessionOptInRequired: Boolean?- Apakah pengguna harus memilih untuk menggunakan alur pasca-sesi secara eksplisit sebelum alur tersebut berjalan. Saattrue, tampilkan dialog keikutsertaan di UI Anda dan hanya panggilstartPostSession()setelah pengguna memberikan izin. Saatfalseataunull, pemeriksaan keikutsertaan tidak diperlukan dan Anda dapat langsung beralih ke alur pasca-sesi.
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.
}
postSessionRequiredmemberi tahu Anda apakah ada alur pasca-sesi yang dikonfigurasi untuk sesi tersebut.postSessionOptInRequiredmemberi tahu Anda apakah izin pengguna eksplisit diperlukan sebelum menjalankannya.postSessionTransferStatusmemberi tahu Anda posisi platform dalam siklus proses transisi.
Ketiganya independen - selalu periksa setiap bagian.
Agen virtual pasca-sesi (prioritas tertinggi)
Yang disediakan SDK: Status transisi untuk mengalihkan sesi dari agen manusia kembali ke bot otomatis, dan perutean pesan bot.
Yang Anda bangun: Tidak ada yang benar-benar baru. Karena VA pasca-sesi beroperasi sebagai chat, Anda tetap membuka UI chat kustom yang ada dan membiarkannya menangani pesan otomatis yang masuk seperti yang dilakukannya saat menangani pesan agen manusia.
Contoh penerapan
Jika SDK menandakan transisi ke VA pasca-sesi, Anda memperbarui UI chat untuk mencerminkan perubahan tersebut.
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).
}
Survei (prioritas sedang)
Yang disediakan SDK: rateService mengekspos metode untuk mengambil pertanyaan survei dan mengirimkan jawaban untuk sesi komunikasi. Pertanyaan survei dikonfigurasi oleh administrator untuk setiap antrean dan dapat mencakup beberapa jenis pertanyaan (CSAT, rating bintang, teks bentuk bebas, enumerasi, skala numerik).
Yang akan Anda bangun: Formulir dinamis yang mengambil survei, mengulang pertanyaan, merender UI yang sesuai (seperti tombol pilihan, rating bintang, atau kolom teks) untuk setiap pertanyaan, memvalidasi bahwa kolom wajib diisi telah diisi, dan mengemas jawaban untuk dikirim kembali.
Jenis kunci
// 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
)
Contoh penerapan
Kode ini mengambil pertanyaan survei dari rateService, merendernya dalam fragmen kustom, dan mengirimkan jawaban.
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 - kepuasan pelanggan (prioritas terendah)
Yang disediakan SDK: rateService mengekspos metode rate() untuk mengirimkan skor CSAT dan masukan opsional untuk sesi komunikasi. Rating biasanya berupa nilai bilangan bulat 1–5.
Yang Anda buat: Layar penilaian visual (seperti deretan bintang yang dapat diketuk atau skala angka 1–5), kolom teks jika masukan diizinkan, dan tombol Kirim.
Jenis kunci
// Request payload for rate()
data class RatingRequest(
val rating: Int? = null, // Typically 1-5
val feedback: String? = null // Optional qualitative feedback
)
Contoh penerapan
Setelah pengguna mengetuk rating bintang dan menuliskan masukan, Anda akan meneruskan nilai tersebut ke 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
}
}
}
Mendownload transkrip chat
Yang disediakan SDK: Metode untuk mendownload transkrip chat sebagai file PDF.
Yang Anda buat: Tombol download di UI dan logika penanganan file (simpan, bagikan, atau pratinjau).
Contoh penerapan
Kode ini menunjukkan pendekatan download transkrip Android - mengambil PDF sebagai file, lalu menyimpan, membagikan, atau melihat pratinjaunya di aplikasi Anda.
// 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}")
}
}
Metode downloadChatTranscript() Android tidak menggunakan parameter dan menampilkan File? yang mengarah ke PDF yang didownload. Metode downloadChatTranscriptData() (yang menampilkan data PDF mentah langsung dalam memori tanpa menyimpan ke disk) hanya tersedia di iOS. Pastikan aplikasi Anda memiliki izin penyimpanan file yang sesuai jika menyimpan ke penyimpanan bersama.
Bahasa dan pelokalan
Karena Anda membuat UI bawaan kustom menggunakan Headless Mobile SDK, Anda memiliki terjemahan tombol, menu, dan pemberitahuan kustom aplikasi Anda.
Jadi, mengapa SDK perlu mengetahui bahasa pengguna?
Perutean antrean: CCAI Platform menggunakan kode bahasa untuk merutekan pengguna ke agen yang berbicara dalam bahasa tersebut.
Pesan sistem: Pesan otomatis yang dibuat oleh backend Platform CCAI (misalnya, "Agen akan segera membantu Anda") disampaikan dalam bahasa aktif.
API layanan bahasa
Yang disediakan SDK: Properti languageCode baca dan tulis untuk bahasa SDK aktif.
Yang Anda bangun: UI pemilih bahasa (jika Anda ingin pengguna memilih secara manual) dan logika untuk menyetel bahasa sebelum sesi dimulai.
// Read the active language
val currentLanguage = CCAI.languageService?.languageCode
// Set the active language before the user starts a support session
CCAI.languageService?.languageCode = "es"
SDK tidak menampilkan daftar bahasa tetap yang didukung SDK. Jika aplikasi Anda menampilkan pemilih bahasa, pertahankan daftar tampilan aplikasi sendiri atau gunakan konfigurasi instance atau perusahaan yang ditampilkan oleh SDK untuk menentukan bahasa yang akan ditawarkan.
Menetapkan bahasa SDK (aturan pra-sesi)
Yang disediakan SDK: LanguageService yang membandingkan bahasa yang Anda minta dengan bahasa yang sebenarnya diaktifkan admin Anda di portal Platform CCAI.
Yang Anda bangun: Jika Anda ingin mengizinkan pengguna mengganti bahasa secara manual (daripada hanya mengandalkan setelan default perangkat mereka), Anda harus membangun UI pemilihan (seperti menu drop-down) dan memastikan UI tersebut hanya dapat diakses sebelum mereka mengetuk saluran dukungan.
Contoh penerapan
Kode ini menunjukkan cara mengganti bahasa SDK secara eksplisit menggunakan kode bahasa ISO 639 standar (misalnya, "es" untuk bahasa Spanyol, "fr" untuk bahasa Prancis). Bahasa pilihan ditetapkan menggunakan parameter languageCode selama inisialisasi SDK di class data InitOptions. Anda juga dapat menyetelnya saat melakukan inisialisasi ulang 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}")
}
Inisialisasi modul chat (Android)
Jika Anda menginisialisasi modul chat menggunakan kelebihan beban yang menerima kode bahasa, Anda juga dapat memberikan bahasa di sana:
val chatOptions = ChatOptions(
webFormInterface = WebFormManager(),
downloadTranscriptVisibility = DownloadTranscriptVisibility.SHOW_ALL,
greeting = "Hello! How can I help you?"
)
CCAI.initializeChat(context = this, options = chatOptions)
Anda juga dapat meneruskan bahasa saat mengambil menu antrean:
val menuResult = CCAI.queueMenuService?.get(key = null)
Ganti per sesi menggunakan ChatRequest:
val chatRequest = ChatRequest(
chat = Chat(
menuId = menuId,
languageCode = "fr" // This session will use French
)
)
Cara CCAI Platform menyelesaikan bahasa akhir
Jika Anda tidak menetapkan bahasa secara eksplisit, SDK tetap memerlukan kode bahasa untuk perutean dan pesan sistem. Untuk menjaga perilaku tetap dapat diprediksi, sebaiknya Anda menetapkannya sendiri sebelum pengguna memulai sesi dukungan.
Gunakan urutan prioritas ini:
Bahasa eksplisit (direkomendasikan): Jika Anda menetapkan bahasa di
InitOptions.languageCode, SDK akan menggunakan bahasa tersebut.Pembaruan bahasa sebelum sesi (masih eksplisit): Jika Anda menyetel
CCAI.languageService?.languageCode = "es"sebelum pengguna memasuki antrean, SDK akan menggunakan bahasa yang diperbarui tersebut.Penggantian per sesi (khusus chat): Jika Anda memberikan
ChatRequest(chat = Chat(..., languageCode = "fr")), sesi chat tersebut akan menggunakan kode bahasa yang diminta.Jika Anda tidak menyetel apa pun: Perilaku akan kembali ke opsi terbaik yang tersedia di konfigurasi perangkat dan instance yang ditentukan oleh SDK. Untuk mendapatkan hasil yang konsisten, hindari mengandalkan hal ini dan selalu tetapkan bahasa secara eksplisit (Langkah 1–3).
Penanganan error, penggantian, dan pengelolaan sesi
Bahkan aplikasi yang paling andal pun dapat mengalami koneksi terputus, dependensi yang tidak ada, atau masalah autentikasi. Headless Mobile SDK dirancang untuk mendeteksi masalah platform yang mendasarinya dan menampilkannya ke aplikasi Anda, sehingga Anda dapat menanganinya dengan baik dalam UI kustom atau mengandalkan penggantian otomatis CCAI Platform.
Menangani error SDK
Yang disediakan SDK: Dua kategori pengecualian yang diketik, bersama dengan pengecualian Kotlin standar:
HttpExceptionWithCode- Ditampilkan oleh metode layanan asinkron apa pun saat permintaan HTTP ke platform CCAI gagal. Mengekspos propertiInt codesehingga Anda dapat membuat cabang pada kode status (misalnya,401untuk autentikasi yang sudah tidak berlaku,429untuk pembatasan frekuensi,5xxuntuk error server).CallCreationError.AfterHours- Error tertutup yang ditampilkan oleh API panggilan terjadwal (callService.createScheduledCall) saat terjadi konflik slot atau periode penjadwalan telah ditutup. MembawadisplayMessageyang dilokalkan server dan ditujukan untuk ditampilkan langsung kepada pengguna.Pengecualian Kotlin atau Java standar - Kegagalan jaringan muncul sebagai
java.io.IOException, pembatalan coroutine sebagaikotlinx.coroutines.CancellationException, dan hal yang tidak terduga sebagai class dasarException.
Yang Anda bangun: Upaya atau penangkapan berlapis di sekitar setiap panggilan layanan SDK. Tangkap pengecualian yang paling spesifik terlebih dahulu (error tertutup khusus domain seperti CallCreationError.AfterHours jika berlaku, lalu HttpExceptionWithCode untuk percabangan status HTTP), dan kembali ke handler Exception generik untuk kegagalan jaringan, sistem, dan yang tidak terduga.
Contoh penerapan
Kode ini membungkus panggilan SDK dalam pola coba atau tangkap berlapis, menangkap HttpExceptionWithCode terlebih dahulu untuk membuat cabang pada kode status HTTP, lalu kembali ke pengendali generik untuk yang lainnya.
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.")
}
}
Praktik terbaik
Bungkus setiap panggilan layanan SDK dalam try/catch berlapis. Gunakan
HttpExceptionWithCodeuntuk membuat cabang pada kode status HTTP - misalnya, memicu ulangccaiShouldAuthenticate()pada401(token autentikasi yang habis masa berlakunya), menerapkan penundaan pada429(pembatasan kecepatan), atau menampilkan error umum pada respons5xx.Untuk API panggilan terjadwal, tangkap
CallCreationError.AfterHourssebelumHttpExceptionWithCodesehingga konflik slot 409 ditangani dengan pesan yang dilokalkan server.Terakhir, tangkap
Exceptiongenerik sebagai jaring pengaman untukIOException,CancellationException, dan kegagalan yang tidak terduga. Mencatat detail pengecualian selama pengembangan membantu menemukan penyebab utama dengan cepat.
Memeriksa dan menghapus sesi
Karena siklus proses aplikasi Anda terpisah dari siklus proses dukungan Platform CCAI, Anda perlu mengelola status sesi - terutama terkait logout pengguna.
Yang disediakan SDK: API utilitas untuk memeriksa apakah sesi dukungan masih berlangsung (termasuk setelah aplikasi dimulai ulang atau keluar paksa), dan metode untuk menghapus status autentikasi SDK yang di-cache dan terkait dengan pengguna akhir tertentu - mencegah pengguna yang login berikutnya mewarisi sesi pengguna sebelumnya.
Apa yang Anda bangun: Pemeriksaan sebelum penerbangan sebelum mencoba memulai sesi baru, dan rutin pembersihan yang dipicu saat pengguna logout dari aplikasi utama Anda.
Contoh penerapan (pembersihan logout)
Mengakhiri sesi aktif, menghapus token autentikasi yang di-cache dari SDK, menghapus data aplikasi lokal, dan membuka layar login - pembersihan minimum yang diperlukan untuk mencegah pewarisan sesi saat pengguna logout.
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()
}
}
Integrasi coroutine dan Flow Kotlin
Headless Mobile SDK for Android didesain dengan Coroutine Kotlin dan Flow sebagai pola reaktif utamanya. Ini adalah integrasi Combine yang setara dengan Android SDK untuk iOS.
Yang disediakan SDK: Aliran Kotlin Flow dan fungsi penangguhan yang memancarkan peristiwa SDK real-time (pesan masuk, perubahan status berbagi layar) sebagai aliran reaktif yang dapat diamati.
Yang Anda bangun: Integrasi berbasis coroutine yang mengumpulkan Flow ini dan memperbarui UI Android Anda.
Menangguhkan fungsi
Banyak metode layanan SDK adalah fungsi penangguhan Kotlin, yang berarti metode tersebut dapat dipanggil dari dalam cakupan coroutine dan akan ditangguhkan (tidak diblokir) saat menunggu hasil.
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)
}
}
Alur layanan chat
chatService menampilkan properti SharedFlow Kotlin untuk mengamati peristiwa chat real-time. Kumpulkan dalam cakupan coroutine yang mendukung siklus proses (lifecycleScope atau viewModelScope).
| Flow | Memancarkan |
|---|---|
messagesReceived |
Daftar pesan chat masuk (dari agen, VA, atau sistem). |
typingEvent |
Saat pengguna atau agen sedang mengetik. |
memberEvent |
Saat pengguna atau agen bergabung atau keluar dari percakapan (termasuk pengalihan dan pemutusan koneksi agen). |
stateChanged |
Perubahan status penyedia chat (menghubungkan, terhubung, terputus, error). |
chatReceived |
Data respons chat saat chat diterima atau diperbarui (termasuk perubahan status, penetapan agen, dan status pasca-sesi). |
chatService mengekspos Flow tambahan untuk peristiwa transfer (transferEventSubject), hasil pemeriksaan status (checkStatusSubject), dan hasil pengiriman pesan (messageSendResultSubject).
Contoh penerapan
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)
}
}
Status berbagi layar
Yang disediakan SDK: Status sesi berbagi layar dikirimkan melalui callback onSessionStateChanged di ScreenShareCallbacks, yang Anda teruskan ke ScreenShareManager.startSession() — lihat Headless Mobile SDK for Android: Voice, screen share, scheduled call, and email. Callback diaktifkan setiap kali sesi bertransisi antara INACTIVE, PENDING, dan ACTIVE.
Apa yang Anda bangun: Handler yang terhubung ke ScreenShareCallbacks.onSessionStateChanged yang mengupdate UI saat sesi bertransisi antar-status, ditambah properti lokal (misalnya, currentScreenShareSessionState) jika Anda perlu membaca status saat ini di luar callback.
Contoh penerapan
Selain jalur ScreenShareCallbacks, ScreenShareManager juga mengekspos API addStateChangeListener / removeStateChangeListener mandiri dan aksesor getSessionState() sinkron. Gunakan ini saat Anda perlu mengamati status dari komponen selain yang memanggil startSession(), atau untuk melakukan pembacaan non-reaktif satu kali. Tidak seperti jalur ScreenShareCallbacks - tempat callback dilepaskan secara otomatis saat sesi berakhir - pendengar mandiri tidak dilepaskan secara otomatis. Panggil removeStateChangeListener(...) dalam pembongkaran pemilik siklus proses Anda untuk menghindari kebocoran.
// 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)
Data kustom
Yang disediakan SDK: Mekanisme untuk melampirkan metadata terstruktur ke sesi chat, baik sebagai JWT bertanda tangan (diverifikasi oleh backend Anda) atau sebagai payload tanpa tanda tangan (ditetapkan langsung di klien).
Yang Anda buat: Logika pengumpulan data di aplikasi Anda dan, untuk data bertanda tangan, endpoint penandatanganan JWT di server backend Anda (lihat Headless Mobile SDK for Android: Mulai untuk skema payload JWT dan autentikasi lengkap).
Data kustom bertanda tangan
Data kustom bertanda tangan menggunakan token JWT yang ditandatangani dengan rahasia perusahaan Anda di server backend. Ini adalah pendekatan yang aman untuk meneruskan data sensitif atau tepercaya.
Contoh penerapan
Buat objek data kustom bertanda tangan dari JWT yang dibuat di backend Anda.
// JWT signed with company secret (signing must happen on your backend)
val customData = CustomData(signed = "header.payload.signature")
Data kustom yang tidak bertanda tangan
Data kustom yang tidak bertanda tangan ditetapkan langsung di klien menggunakan CustomDataPayload dan CustomDataItem.
Contoh penerapan
Buat payload yang tidak bertanda tangan dengan item data yang terlihat dan disembunyikan agen. CustomDataPayload mengambil MutableMap<String, CustomDataItem?>, jadi gunakan penetapan subskrip (atau mutableMapOf) untuk menambahkan setiap item berdasarkan kunci. Kunci yang Anda pilih (misalnya, "user_name" atau "account_tier") akan menjadi nama kolom yang ditampilkan ke agen dan untuk perutean/analisis.
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)
Item data kustom mendukung properti berikut:
label: Label tampilan yang ditampilkan kepada agen.value: Nilai data (contoh:String,Int,Bool).type: Jenis data (STRING,NUMBER,DATE,URL,BOOLEAN).invisibleToAgent: Jikatrue, data disembunyikan dari agen, tetapi tersedia untuk perutean dan analisis.
Menggunakan data kustom dalam permintaan chat
Lampirkan data kustom saat memulai sesi chat.
Contoh penerapan
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}")
}
}
Pengalihan chat eksternal
Yang disediakan SDK: Struktur data (ExternalChatTransfer) untuk meneruskan nama agen sebelumnya dan transkrip percakapan ke sesi CCAI Platform baru.
Apa yang Anda bangun: Alur migrasi yang mengekstrak konteks dari sistem chat eksternal Anda dan mengemasnya untuk SDK.
Contoh penerapan
Kode ini membuat objek transfer chat eksternal dengan nama agen sebelumnya dan melampirkannya ke data kustom sesi.
// 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)
Tindakan ini berguna saat memigrasikan pengguna dari sistem chat pihak ketiga ke sesi yang didukung Platform CCAI, sehingga mempertahankan konteks percakapan untuk agen penerima.
Pertimbangan threading
Headless Mobile SDK for Android dirancang untuk bekerja dengan coroutine Kotlin dan sebagian besar aman untuk thread utama. Namun, ada pertimbangan threading penting untuk integrasi tanpa UI.
Keamanan thread utama
Semua metode SDK aman untuk thread dan dapat dipanggil dari thread mana pun. Sebagian besar panggilan layanan SDK (misalnya, chatService?.start(), queueMenuService?.get()) adalah fungsi penangguhan yang menangani threading-nya sendiri secara internal - fungsi tersebut menangguhkan coroutine panggilan tanpa memblokir thread. Anda dapat memanggilnya dengan aman dari thread utama dalam cakupan 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 dan thread latar belakang
Metode ccaiShouldAuthenticate() di CCAIDelegate Anda adalah fungsi penangguhan, sehingga Anda dapat melakukan panggilan jaringan dengan aman di dalamnya:
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
}
}
Update UI dari callback SDK
Saat mengamati peristiwa SDK menggunakan Flow, pastikan update UI terjadi di thread utama. Jika Anda menggunakan lifecycleScope, hal ini akan ditangani secara otomatis:
// lifecycleScope dispatches to Main by default
lifecycleScope.launch {
chatMessagesFlow.collect { messages ->
// Already on Main - safe to update UI
chatAdapter.submitList(messages)
}
}
Jika Anda mengumpulkan dari pengirim non-utama, beralih kembali secara eksplisit:
lifecycleScope.launch(Dispatchers.IO) {
// Processing on IO thread
val processed = heavyProcessing()
withContext(Dispatchers.Main) {
// Update UI on Main thread
updateChatUi(processed)
}
}
Threading inisialisasi
Panggilan inisialisasi modul CCAI.initialize() dan terkait, seperti initializeChat(), initializeCall(), dan initializeScreenShare(), harus dipanggil di thread utama di Application.onCreate() Anda. Ini adalah panggilan sinkron yang menyiapkan status internal 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)
}
}
Pengelolaan memori
Pengelolaan memori yang tepat sangat penting untuk mencegah kebocoran dan memastikan pelepasan resource SDK yang bersih.
Siklus proses Coroutine dan Flow
Selalu batalkan referensi Job coroutine dan pengumpul Flow dalam metode siklus proses Android (onDestroy() untuk Aktivitas, onCleared() untuk ViewModel). Jika tidak dilakukan, referensi dapat tetap aktif lebih lama dari yang diinginkan, sehingga menyebabkan kebocoran memori.
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()
}
}
Referensi pemroses dan callback
Gunakan referensi lemah untuk pemroses dan callback guna mencegah siklus retensi. Hapus semua callback terdaftar saat Aktivitas atau ViewModel Anda dihancurkan.
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()
}
}
Praktik terbaik
Simpan referensi coroutine
Job: Selalu simpan referensiJobyang ditampilkan olehlaunch {}untuk mencegah coroutine yang tidak terkendali yang bertahan lebih lama dari cakupannya.Membersihkan dalam metode siklus proses: Batalkan semua tugas dan hapus callback di
onDestroy()(Aktivitas) atauonCleared()(ViewModel).Gunakan referensi lemah untuk pemroses: Berhati-hatilah dengan lambda yang secara implisit mengambil
this(konteks Activity atau Fragment yang melampirkan), karena hal ini dapat mencegah pembersihan sampah memori.Layanan dikelola SDK: Anda tidak perlu merilis layanan SDK secara manual - layanan tersebut dikelola oleh objek singleton
CCAI.
Pemecahan masalah
Bagian ini membahas masalah integrasi umum dan solusinya.
Masalah build dan dependensi
Bagian ini membahas masalah umum build dan dependensi.
Masalah
Gradle tidak dapat memuat com.ccaiplatform.android:CCAIKit.
Penyebab: Repositori Maven CCAI tidak ada atau menggunakan URL yang salah.
Perbaikan: Pastikan
settings.gradle.ktsAnda menyertakan:maven { url = uri("https://sdk.ujet.co/ccaip/android/") }Perhatikan segmen jalur
/ccaip/. URL lama (https://sdk.ujet.co/android/) tidak akan menyelesaikan artefak.
Masalah
Java 17 memerlukan error kompilasi.
Penyebab: SDK ini memerlukan kompatibilitas Java 17.
Perbaikan: Perbarui
build.gradle.ktsAnda:compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { jvmTarget = "17" }
Masalah
chatLibraryNotFound (kode error 1100) saat runtime.
Penyebab: Modul
CCAIChatRedtidak ada dalam dependensi.Perbaikan:
CCAIChatdanCCAIChatReddiperlukan untuk chat. Tambahkan:implementation("com.ccaiplatform.android:CCAIChat:ccaiVersion") implementation("com.ccaiplatform.android:CCAIChatRed:ccaiVersion")
Masalah inisialisasi
Bagian ini membahas masalah inisialisasi umum.
Masalah
NullPointerException saat mengakses CCAI.chatService.
Penyebab:
CCAI.initializeChat()tidak dipanggil, atau dipanggil sebelumCCAI.initialize().Perbaikan: Pastikan urutan inisialisasi di
Application.onCreate():CCAI.initialize(context, options = initOptions)CCAI.initializeChat(context, options = chatOptions)CCAI.initializeCall(context)(jika menggunakan panggilan suara atau terjadwal)CCAI.initializeScreenShare(context, options)(jika menggunakan berbagi layar)
Masalah
Layanan SDK menampilkan null setelah inisialisasi.
Penyebab:
CCAI.initialize()tidak dipanggil di thread utama, atau classApplicationtidak terdaftar di manifes.Perbaiki: Pastikan baris
android:name=".MainApplication"ada di fileAndroidManifest.xmlAnda dan semua panggilan init ada dionCreate().
Masalah Authentication
Bagian ini membahas masalah autentikasi umum.
Masalah
Autentikasi gagal tanpa pemberitahuan atau ccaiShouldAuthenticate() tidak pernah dipanggil.
Penyebab:
CCAIDelegatetidak ditetapkan diInitOptions.Perbaikan: Pastikan
delegatediteruskan keInitOptions:val initOptions = InitOptions( key = "YOUR_KEY", urlHost = "your_subdomain.ccaiplatform.com", delegate = MyCCAIDelegate() // Must not be null )
Masalah
Error validasi JWT (kode error 101).
Penyebab: JWT sudah tidak berlaku, formatnya salah, atau ditandatangani dengan secret yang salah.
Perbaikan: Verifikasi bahwa backend Anda:
Menggunakan Kode Rahasia Perusahaan yang benar.
Menetapkan stempel waktu
iatdanexpyang valid.Menampilkan string JWT yang diformat dengan benar.
Masalah notifikasi push
Bagian ini membahas masalah umum notifikasi push.
Masalah
Notifikasi push tidak diterima atau ditangani.
Penyebab: Token FCM tidak terdaftar di SDK, layanan Firebase Messaging tidak terdaftar, atau payload data masuk tidak diteruskan ke SDK.
Perbaikan:
Panggil
CCAI.pushNotificationService?.updatePushToken(token)dionNewToken()dan saat startup.Teruskan
remoteMessage.datakeCCAI.pushNotificationService?.handlePushNotification(remoteMessage.data)dari layanan pesan Firebase Anda.Pastikan
google-services.jsonada di modul aplikasi Anda.Pastikan Kunci Akun Layanan Firebase dikonfigurasi di portal administrator Platform CCAI.
Merender UI panggilan masuk atau notifikasi yang terlihat dalam kode aplikasi Anda sendiri.
Masalah
SDK tidak bereaksi terhadap notifikasi push yang masuk (pesan diabaikan).
Penyebab: Meneruskan jenis data yang salah ke metode
handlePushNotification().Perbaikan: Pastikan Anda meneruskan
remoteMessage.data(yang merupakanMap<String, String>), bukan seluruh objekRemoteMessage.
Masalah sesi chat
Bagian ini membahas masalah umum sesi chat.
Masalah
chatService?.start() akan menampilkan pengecualian atau menampilkan null.
Penyebab:
menuIdtidak valid, kegagalan autentikasi, atau masalah jaringan.Perbaikan:
Pastikan
menuIdcocok dengan antrean yang valid di portal administrator Anda.Pastikan
ccaiShouldAuthenticate()menukar JWT bertanda tangan yang valid melaluiCCAI.authService?.authenticate(jwt)dan menampilkan token autentikasi yang dihasilkan.Pastikan perangkat memiliki konektivitas jaringan.
Gabungkan panggilan dalam try atau catch, lalu catat detail pengecualian.
Masalah
Pesan chat tidak muncul di UI Anda.
Penyebab: Tidak mengamati aliran pesan layanan chat.
Perbaikan: Pastikan Anda mengumpulkan dari Flow layanan chat dalam cakupan yang mendukung siklus proses (lihat bagian Alur layanan chat di atas).
Masalah berbagi layar
Bagian ini membahas masalah umum berbagi layar.
Masalah
Berbagi layar tidak dimulai setelah pengguna memberikan izin.
Penyebab: Modul
CCAIScreenSharetidak diinisialisasi atauMediaProjectionAndroid tidak dimulai.Perbaikan:
Pastikan
CCAI.initializeScreenShare()dipanggil denganScreenShareOptionsyang valid.Pastikan Anda memulai
MediaProjectionManagerdan layanan latar depan Android dengan benar setelah mendapatkan izin.Di Android 14+, pastikan jenis layanan latar depan Anda menyertakan
mediaProjection.
Tips proses debug umum
Aktifkan logging panjang selama pengembangan untuk melihat status internal SDK.
Periksa portal administrator CCAI Platform untuk memverifikasi konfigurasi antrean, pengaktifan saluran, dan jam operasional.
Pantau traffic jaringan dengan alat seperti Charles Proxy atau Network Inspector Android Studio untuk memverifikasi panggilan API ke
*.ccaiplatform.com.Pastikan versi modul cocok - semua modul
com.ccaiplatform.androidharus menggunakan nomor versi yang sama untuk menghindari masalah kompatibilitas.
Penutup seri
Selamat - Anda telah menyelesaikan seri lima bagian Headless Mobile SDK for Android. Integrasi Anda kini mencakup:
Penyiapan dan inisialisasi (Headless Mobile SDK for Android: Memulai)
Antrean, saluran, dan chat (Headless Mobile SDK for Android: Queues, channels, and chat)
Suara, berbagi layar, panggilan terjadwal, dan email (Headless Mobile SDK for Android: Voice, screen share, scheduled call, and email)
Tindakan cerdas, lampiran, dan pengalihan (Headless Mobile SDK for Android: Smart actions, attachments, and deflection)
Alur pasca-sesi, pelokalan, penanganan error, dan topik lanjutan (artikel ini)
Untuk referensi berkelanjutan, beri bookmark pada Headless Mobile SDK - Android API Reference dan perhatikan catatan rilis CCAI Platform untuk mengetahui update SDK.