SDK seluler tanpa UI untuk Android: Topik lanjutan, pelokalan, dan alur pasca-sesi

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:

  1. Agen virtual (VA) pasca-sesi: Chatbot otomatis yang melanjutkan percakapan setelah agen manusia mengakhiri percakapan.

  2. Survei: Formulir multi-pertanyaan yang disesuaikan dengan antrean tertentu.

  3. 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 emisi ChatResponse berikutnya (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. Saat true, tampilkan dialog keikutsertaan di UI Anda dan hanya panggil startPostSession() setelah pengguna memberikan izin. Saat false atau null, 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.
}
  • postSessionRequired memberi tahu Anda apakah ada alur pasca-sesi yang dikonfigurasi untuk sesi tersebut.

  • postSessionOptInRequired memberi tahu Anda apakah izin pengguna eksplisit diperlukan sebelum menjalankannya.

  • postSessionTransferStatus memberi 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?

  1. Perutean antrean: CCAI Platform menggunakan kode bahasa untuk merutekan pengguna ke agen yang berbicara dalam bahasa tersebut.

  2. 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:

  1. Bahasa eksplisit (direkomendasikan): Jika Anda menetapkan bahasa di InitOptions.languageCode, SDK akan menggunakan bahasa tersebut.

  2. Pembaruan bahasa sebelum sesi (masih eksplisit): Jika Anda menyetel CCAI.languageService?.languageCode = "es" sebelum pengguna memasuki antrean, SDK akan menggunakan bahasa yang diperbarui tersebut.

  3. Penggantian per sesi (khusus chat): Jika Anda memberikan ChatRequest(chat = Chat(..., languageCode = "fr")), sesi chat tersebut akan menggunakan kode bahasa yang diminta.

  4. 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 properti Int code sehingga Anda dapat membuat cabang pada kode status (misalnya, 401 untuk autentikasi yang sudah tidak berlaku, 429 untuk pembatasan frekuensi, 5xx untuk error server).

  • CallCreationError.AfterHours - Error tertutup yang ditampilkan oleh API panggilan terjadwal (callService.createScheduledCall) saat terjadi konflik slot atau periode penjadwalan telah ditutup. Membawa displayMessage yang dilokalkan server dan ditujukan untuk ditampilkan langsung kepada pengguna.

  • Pengecualian Kotlin atau Java standar - Kegagalan jaringan muncul sebagai java.io.IOException, pembatalan coroutine sebagai kotlinx.coroutines.CancellationException, dan hal yang tidak terduga sebagai class dasar Exception.

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 HttpExceptionWithCode untuk membuat cabang pada kode status HTTP - misalnya, memicu ulang ccaiShouldAuthenticate() pada 401 (token autentikasi yang habis masa berlakunya), menerapkan penundaan pada 429 (pembatasan kecepatan), atau menampilkan error umum pada respons 5xx.

  • Untuk API panggilan terjadwal, tangkap CallCreationError.AfterHours sebelum HttpExceptionWithCode sehingga konflik slot 409 ditangani dengan pesan yang dilokalkan server.

  • Terakhir, tangkap Exception generik sebagai jaring pengaman untuk IOException, 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: Jika true, 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 referensi Job yang ditampilkan oleh launch {} 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) atau onCleared() (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.kts Anda 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.kts Anda:

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

Masalah

chatLibraryNotFound (kode error 1100) saat runtime.

  • Penyebab: Modul CCAIChatRed tidak ada dalam dependensi.

  • Perbaikan: CCAIChat dan CCAIChatRed diperlukan 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 sebelum CCAI.initialize().

  • Perbaikan: Pastikan urutan inisialisasi di Application.onCreate():

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

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

    3. CCAI.initializeCall(context) (jika menggunakan panggilan suara atau terjadwal)

    4. CCAI.initializeScreenShare(context, options) (jika menggunakan berbagi layar)

Masalah

Layanan SDK menampilkan null setelah inisialisasi.

  • Penyebab: CCAI.initialize() tidak dipanggil di thread utama, atau class Application tidak terdaftar di manifes.

  • Perbaiki: Pastikan baris android:name=".MainApplication" ada di file AndroidManifest.xml Anda dan semua panggilan init ada di onCreate().

Masalah Authentication

Bagian ini membahas masalah autentikasi umum.

Masalah

Autentikasi gagal tanpa pemberitahuan atau ccaiShouldAuthenticate() tidak pernah dipanggil.

  • Penyebab: CCAIDelegate tidak ditetapkan di InitOptions.

  • Perbaikan: Pastikan delegate diteruskan ke InitOptions:

    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 iat dan exp yang 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) di onNewToken() dan saat startup.

    • Teruskan remoteMessage.data ke CCAI.pushNotificationService?.handlePushNotification(remoteMessage.data) dari layanan pesan Firebase Anda.

    • Pastikan google-services.json ada 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 merupakan Map<String, String>), bukan seluruh objek RemoteMessage.

Masalah sesi chat

Bagian ini membahas masalah umum sesi chat.

Masalah

chatService?.start() akan menampilkan pengecualian atau menampilkan null.

  • Penyebab: menuId tidak valid, kegagalan autentikasi, atau masalah jaringan.

  • Perbaikan:

    • Pastikan menuId cocok dengan antrean yang valid di portal administrator Anda.

    • Pastikan ccaiShouldAuthenticate() menukar JWT bertanda tangan yang valid melalui CCAI.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 CCAIScreenShare tidak diinisialisasi atau MediaProjection Android tidak dimulai.

  • Perbaikan:

    • Pastikan CCAI.initializeScreenShare() dipanggil dengan ScreenShareOptions yang valid.

    • Pastikan Anda memulai MediaProjectionManager dan 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.android harus 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:

Untuk referensi berkelanjutan, beri bookmark pada Headless Mobile SDK - Android API Reference dan perhatikan catatan rilis CCAI Platform untuk mengetahui update SDK.