‫Headless mobile SDK ל-Android: נושאים מתקדמים, לוקליזציה ותהליכים אחרי סיום הסשן

במאמר הזה מוסבר איך לשלב ולהתאים אישית את Headless Mobile SDK באפליקציה ל-Android. הוא כולל נושאים כמו לוקליזציה, תהליכים אחרי סיום הסשן ועוד נושאים מתקדמים.

תהליכי עבודה אחרי סיום הסשן (סוכן וירטואלי אחרי סיום הסשן, סקרים וסקר שביעות רצון)

כששיחה בצ'אט בזמן אמת או שיחה קולית מסתיימת, מרכזי שירות לקוחות רוצים לעיתים קרובות לאסוף משוב או להציע מעקב אוטומטי. פלטפורמת CCAI תומכת בשלושה סוגים של חוויות אחרי סיום השיחה:

  1. נציג וירטואלי (VA) אחרי סיום הסשן: צ'אטבוט אוטומטי שממשיך את השיחה אחרי שהנציג האנושי מתנתק.

  2. סקרים: טופס עם כמה שאלות שמותאם לתור ספציפי.

  3. שביעות רצון הלקוחות (CSAT): דירוג (לדוגמה, כוכב אחד עד 5 כוכבים) ומשוב טקסטואלי אופציונלי.

אכיפת ההיררכיה

אדמינים נוהגים להגדיר כללים חופפים בפורטל (לדוגמה, הפעלת CSAT באופן גלובלי, אבל גם הפעלת סוכן וירטואלי אחרי סיום השיחה לתור לחיוב).

מה ה-SDK מספק: מנוע קפדני לפתרון קונפליקטים. ערכת ה-SDK בודקת את הכללים של הפורטל ומוודאת שרק תהליך אחד של פוסטים יופעל בכל סשן, בהתאם להיררכיה הבאה:

  • העדיפות אחרי הסשן: סיוע וירטואלי אחרי הסשן > סקרים > שביעות רצון לקוחות (CSAT)

    • דוגמה: אם גם CSAT וגם VA אחרי סיום הסשן מופעלים בתור, המשתמש יראה רק את ה-VA אחרי סיום הסשן.

מה יוצרים: רכיב listener שממתין לסיום הסשן, שואל את ה-SDK איזה תהליך ניצח בהערכת ההיררכיה ומציג את המסך המתאים. לא צריך לכתוב לוגיקת if-else מורכבת כדי להבין איזו חוויה מנצחת. ה-SDK מבצע את החישובים האלה בשבילכם.

דוגמה להטמעה

בקטע הקוד הזה מוצג איך האפליקציה צריכה להאזין לסיום של סשן ולשאול את ה-SDK מה לעשות בהמשך.

// 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)
    }
}

מעבר לשלב שאחרי הסשן

לא משנה איזה תהליך ינצח בהיררכיה, צריך להפעיל שתי שיטות מחזור חיים משותפות ב-chatService כדי לסמן לפלטפורמת CCAI Platform שהשלב שאחרי הסשן התחיל.

מה ה-SDK מספק: methods של מחזור החיים כדי להעביר את הסשן אל השלב שאחרי הסשן ובמהלכו (startPostSession, readyPostSession).

מה שאתם בונים: לוגיקת התיאום שמפעילה את השיטות האלה בנקודות הנכונות בתהליך ממשק המשתמש אחרי הסשן.

דוגמה להטמעה

הקוד הזה מעביר את הסשן דרך שני שלבי מחזור החיים שאחרי הסשן.

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}")
    }
}

אחרי שהפלטפורמה מאשרת את המעבר, ממשיכים לתהליך הספציפי הזה.

שדות של מצב אחרי סשן ב-ChatResponse

שני שדות של תזמור אחרי סשן ב-ChatResponse מניעים את לוגיקת המעבר:

  • ‫postSessionTransferStatus: String? – המצב הנוכחי של ההעברה אחרי סיום השיחה (לדוגמה, "in_progress", ‏ "ready", ‏ "completed"). פלטפורמת startPostSession() משתמשת בשדה הזה כדי להפעיל את מכונת המצבים של תזמור ההעברה: היא מעבירה את הערך ל-"in_progress", ‏ readyPostSession() מעבירה אותו ל-"ready", ואתם יכולים לראות את הערך הזה בפליטות הבאות של ChatResponse (מזרימת הצ'אט של שירות הצ'אט) כדי לאשר את המעבר בצד הפלטפורמה.

  • ‫postSessionOptInRequired: Boolean? – האם המשתמש צריך להביע הסכמה מפורשת להשתתפות בתהליך שאחרי הסשן לפני שהוא מתחיל. כשמגיעים ל-true, מציגים למשתמש הנחיה להצטרפות ומפעילים את startPostSession() רק אחרי שהמשתמש מביע הסכמה. אם התוצאה היא false או null, לא צריך לבצע את בדיקת ההסכמה להצטרפות, ואפשר לעבור ישירות לתהליך שאחרי ההתחברות.

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 מציין אם הוגדר איזשהו תהליך אחרי סיום הסשן עבור הסשן.

  • postSessionOptInRequired מציין אם נדרשת הסכמה מפורשת מהמשתמשים לפני הפעלת הכלי.

  • postSessionTransferStatus מציין את השלב שבו הפלטפורמה נמצאת במחזור החיים של המעבר.

כל שלושת המקורות הם עצמאיים – חשוב לבדוק כל אחד מהם.

נציג וירטואלי אחרי סיום השיחה (העדיפות הכי גבוהה)

מה שערכת ה-SDK מספקת: מצב המעבר להעברת השיחה מנציג אנושי בחזרה לבוט אוטומטי, וניתוב ההודעות של הבוט.

מה יוצרים: שום דבר חדש לגמרי. מכיוון שהסוכן הווירטואלי פועל אחרי סיום השיחה כצ'אט, אתם משאירים את ממשק המשתמש המותאם אישית של הצ'אט פתוח ומאפשרים לו לטפל בהודעות האוטומטיות הנכנסות בדיוק כמו שהוא טיפל בהודעות של הסוכן האנושי.

דוגמה להטמעה

אם ה-SDK מסמן מעבר ל-VA אחרי סיום הסשן, צריך לעדכן את ממשק המשתמש של הצ'אט בהתאם לשינוי.

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).
}

סקרים (עדיפות בינונית)

מה ה-SDK מספק: ה-rateService חושף שיטות לאחזור שאלות בסקר ולשליחת תשובות במהלך סשן תקשורת. האדמין מגדיר את שאלות הסקר לכל תור, והן יכולות לכלול כמה סוגים של שאלות (שביעות רצון לקוחות, דירוג בכוכבים, טקסט חופשי, מנייה, סולם מספרי).

מה יוצרים: טופס דינמי שמביא את הסקר, עובר על השאלות, מעבד את ממשק המשתמש המתאים (למשל, לחצני בחירה, דירוג בכוכבים או שדות טקסט) לכל שאלה, מוודא ששדות החובה מולאו ואורז את התשובות לשליחה חזרה.

סוגי מפתחות

// 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
)

דוגמה להטמעה

הקוד הזה מאחזר שאלות מהסקר rateService, מעבד אותן בקטע בהתאמה אישית ושולח את התשובות.

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()
        }
    }
}

שביעות רצון הלקוחות (העדיפות הנמוכה ביותר)

מה ה-SDK מספק: ה-SDK‏ rateService חושף את ה-method‏ rate() לשליחת ציון שביעות רצון לקוחות (CSAT) ומשוב אופציונלי לסשן תקשורת. הדירוג הוא בדרך כלל מספר שלם בטווח 1-5.

מה יוצרים: מסך דירוג חזותי (למשל, שורה של כוכבים שאפשר להקיש עליהם או סולם מספרים מ-1 עד 5), שדה טקסט אם אפשר להוסיף משוב ולחצן שליחה.

סוגי מפתחות

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

דוגמה להטמעה

אחרי שהמשתמש מקיש על דירוג הכוכבים וכותב את המשוב שלו, מעבירים את הערכים האלה אל 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
        }
    }
}

הורדת תמלילי צ'אט

מה ערכת ה-SDK מספקת: שיטה להורדת תמליל הצ'אט כקובץ PDF.

מה יוצרים: את לחצן ההורדה בממשק המשתמש ואת הלוגיקה של טיפול בקבצים (שמירה, שיתוף או תצוגה מקדימה).

דוגמה להטמעה

הקוד הזה מדגים את הגישה להורדת תמליל ב-Android – אחזור קובץ ה-PDF ושמירה, שיתוף או הצגה שלו בתצוגה מקדימה באפליקציה.

// 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}")
    }
}

השיטה downloadChatTranscript() ב-Android לא מקבלת פרמטרים ומחזירה File? שמצביע על קובץ ה-PDF שהורד. השיטה downloadChatTranscriptData() (שמחזירה נתוני PDF גולמיים ישירות בזיכרון בלי לשמור בדיסק) זמינה רק ב-iOS. אם שומרים באחסון משותף, צריך לוודא שיש לאפליקציה הרשאות מתאימות לאחסון קבצים.

שפות ולוקליזציה

אתם יוצרים ממשק משתמש מותאם אישית באמצעות Headless Mobile SDK, ולכן אתם אחראים לתרגום של הלחצנים, התפריטים וההתראות המותאמות אישית באפליקציה.

אז למה ערכת ה-SDK צריכה לדעת את השפה של המשתמש?

  1. ניתוב לתור: פלטפורמת CCAI משתמשת בקוד השפה כדי לנתב את המשתמש לסוכן שמדבר בשפה הזו.

  2. הודעות מערכת: הודעות אוטומטיות שנוצרות על ידי העורף של פלטפורמת CCAI (לדוגמה, 'נציג יגיע אליך בקרוב') מועברות בשפה הפעילה.

ממשקי API של שירותי שפה

מה ה-SDK מספק: מאפיין languageCode לקריאה ולכתיבה של שפת ה-SDK הפעילה.

מה יוצרים: ממשק משתמש לבחירת שפה (אם רוצים שהמשתמשים יבחרו ידנית) והלוגיקה להגדרת השפה לפני תחילת הסשן.

// 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 לא חושפת רשימה קבועה של שפות שנתמכות על ידי SDK. אם באפליקציה מוצג הכלי לבחירת שפה, צריך לתחזק את רשימת התצוגה של האפליקציה בעצמכם או להשתמש בהגדרות של מופע או של ארגון שמוחזרות על ידי ה-SDK כדי לקבוע אילו שפות להציע.

הגדרת שפת ה-SDK (הכלל שלפני ההפעלה)

מה ה-SDK מספק: LanguageService שמשווה בין השפה שביקשתם לבין השפות שהאדמינים שלכם הפעילו בפועל בפורטל של CCAI Platform.

מה צריך לבנות: אם רוצים לאפשר למשתמשים לשנות את השפה באופן ידני (ולא להסתמך רק על הגדרות ברירת המחדל של המכשיר), צריך לבנות את ממשק המשתמש לבחירת שפה (למשל תפריט נפתח) ולוודא שהגישה אליו אפשרית רק לפני שהמשתמשים מקישים על ערוץ תמיכה.

דוגמה להטמעה

בדוגמה הזו מוצג איך לשנות באופן מפורש את השפה של ה-SDK באמצעות קודי שפה רגילים לפי תקן ISO 639 (למשל, "es" לספרדית, "fr" לצרפתית). השפה המועדפת מוגדרת באמצעות הפרמטר languageCode במהלך אתחול ה-SDK במחלקת הנתונים InitOptions. אפשר גם להגדיר את הערך הזה כשמאתחלים מחדש את ה-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}")
}

הפעלה של מודול הצ'אט (Android)

אם אתם מאתחלים את מודול הצ'אט באמצעות העמסת יתר שמקבלת קוד שפה, אתם יכולים גם לציין את השפה שם:

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

אפשר גם להעביר את השפה כשמאחזרים תפריטים של תורים:

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

אפשר לבטל את ההגדרה לכל סשן באמצעות ChatRequest:

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

איך פלטפורמת CCAI קובעת את השפה הסופית

אם לא מגדירים שפה באופן מפורש, ה-SDK עדיין צריך קוד שפה לניתוב ולהודעות מערכת. כדי שההתנהגות תהיה צפויה, מומלץ להגדיר את ההגדרה בעצמכם לפני שהמשתמש מתחיל סשן תמיכה.

משתמשים בסדר העדיפויות הזה:

  1. שפה גסה (מומלץ): אם מגדירים שפה ב-InitOptions.languageCode, ה-SDK משתמש בשפה הזו.

  2. עדכון השפה לפני תחילת הסשן (עדיין מפורש): אם מגדירים את CCAI.languageService?.languageCode = "es" לפני שהמשתמש מצטרף לתור, ה-SDK משתמש בשפה המעודכנת הזו.

  3. שינוי שפה לסשן (בצ'אט בלבד): אם תספקו את הערך ChatRequest(chat = Chat(..., languageCode = "fr")), בסשן הצ'אט הזה תוגדר שפת הקוד המבוקשת.

  4. אם לא מגדירים כלום: ההתנהגות תהיה בהתאם לאפשרות הטובה ביותר שזמינה במכשיר ובהגדרת המופע, כפי שנקבע על ידי ה-SDK. כדי לקבל תוצאות עקביות, מומלץ לא להסתמך על ההגדרה הזו ולהגדיר תמיד שפה באופן מפורש (שלבים 1 עד 3).

טיפול בשגיאות, חזרה למצב קודם וניהול סשנים

גם באפליקציות הכי חזקות יש ניתוקים, תלות חסרה או בעיות באימות. ‫Headless Mobile SDK נועד לזהות את הבעיות הבסיסיות האלה בפלטפורמה ולהציג אותן באפליקציה, כדי שתוכלו לטפל בהן בצורה חלקה בממשק המשתמש המותאם אישית שלכם או להסתמך על מעברי החזרה האוטומטיים של CCAI Platform.

טיפול בשגיאות ב-SDK

מה ה-SDK מספק: שתי קטגוריות של חריגים מוקלדים, לצד חריגים סטנדרטיים של Kotlin:

  • ‫HttpExceptionWithCode – שגיאה שמוחזרת מכל שיטת שירות אסינכרונית כשבקשת HTTP לפלטפורמת CCAI נכשלת. חושף מאפיין Int code כדי שתוכלו ליצור הסתעפות על קודי סטטוס (לדוגמה, 401 לאימות שפג תוקפו, 429 להגבלת קצב הבקשות, 5xx לשגיאות בשרת).

  • ‫CallCreationError.AfterHours – שגיאה סגורה שמוחזרת על ידי API של שיחות מתוזמנות (callService.createScheduledCall) כשמתרחש עימות בין משבצות זמן או כשחלון לפגישות נסגר. התג מכיל displayMessage שמותאמים למיקום השרת ומיועדים להצגה ישירה למשתמש.

  • חריגים רגילים ב-Kotlin או ב-Java – כשלים ברשת מוצגים כ-java.io.IOException, ביטול של שגרת המשך (coroutine) מוצג כ-kotlinx.coroutines.CancellationException, וכל דבר לא צפוי מוצג כמחלקה הבסיסית Exception.

מה יוצרים: שכבת try או catch סביב כל קריאה לשירות SDK. קודם צריך לטפל בחריג הספציפי ביותר (שגיאות סגורות שספציפיות לדומיין, כמו CallCreationError.AfterHours, אם רלוונטי, ואז HttpExceptionWithCode לפי הסתעפות של סטטוס HTTP), ואז לחזור לטיפול כללי ב-Exception בשגיאות ברשת, במערכת ובשגיאות לא צפויות.

דוגמה להטמעה

הקוד הזה עוטף קריאה ל-SDK בתבנית try או catch שכבתית, ומבצע catch ל-HttpExceptionWithCode קודם כדי לבצע הסתעפות לפי קוד מצב ה-HTTP, ואז חוזר ל-handler כללי לכל השאר.

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.")
    }
}

שיטות מומלצות

  • עוטפים כל קריאה לשירות SDK ב-try/catch שכבות. אפשר להשתמש ב-HttpExceptionWithCode כדי ליצור הסתעפות לפי קודי סטטוס HTTP – לדוגמה, להפעיל מחדש את ccaiShouldAuthenticate() ב-401 (טוקן אימות שתוקפו פג), להחיל השהיה לפני ניסיון חוזר ב-429 (הגבלת קצב של יצירת בקשות) או להציג שגיאה כללית בתגובות 5xx.

  • בממשקי API של שיחות מתוזמנות, צריך לטפל ב-CallCreationError.AfterHours before HttpExceptionWithCode כדי לטפל בהתנגשויות של משבצות זמן עם ההודעה המותאמת לשרת.

  • לבסוף, כדאי להשתמש ב-catch כללי Exception כרשת ביטחון ל-IOException, ל-CancellationException ולכל כשל בלתי צפוי. רישום הפרטים של החריגה במהלך הפיתוח עוזר לזהות במהירות את הסיבות העיקריות.

בדיקה ומחיקה של סשנים

מחזור החיים של האפליקציה שלכם נפרד ממחזור החיים של התמיכה בפלטפורמת CCAI, ולכן אתם צריכים לנהל את מצב הסשן – במיוחד כשמשתמשים מתנתקים.

מה ה-SDK מספק: ממשקי API של כלי עזר לבדיקה אם סשן תמיכה עדיין מתנהל (כולל אחרי הפעלה מחדש של האפליקציה או סגירה בכוח), ושיטות לניקוי מצב האימות של ה-SDK שמאוחסן במטמון וקשור למשתמש קצה ספציפי – כך שהמשתמש הבא שייכנס לחשבון לא יקבל בירושה את הסשן של המשתמש הקודם.

מה בונים: בדיקות לפני הפעלה לפני שמנסים להתחיל סשנים חדשים, ושגרות ניקוי שמופעלות כשמשתמש מתנתק מהאפליקציה הראשית.

דוגמה להטמעה (ניקוי נתונים אחרי יציאה מהחשבון)

הפקודה הזו מסיימת כל סשן פעיל, מוחקת את טוקן האימות ששמור במטמון מ-SDK, מוחקת את נתוני האפליקציה המקומיים ומעבירה את המשתמש למסך הכניסה. זהו הניקוי המינימלי שנדרש כדי למנוע ירושה של סשנים כשהמשתמש מתנתק.

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()
    }
}

שילוב של שגרות המשך (coroutine) ו-Flow ב-Kotlin

ה-SDK לנייד ללא ממשק משתמש ל-Android מבוסס על קורוטינות של Kotlin ועל Flow כדפוסי התגובה העיקריים שלו. זוהי הגרסה המקבילה ל-Android של שילוב Combine ב-iOS SDK.

מה ה-SDK מספק: זרמי Kotlin Flow ופונקציות השהיה שפולטות אירועי SDK בזמן אמת (הודעות נכנסות, שינויים במצב שיתוף המסך) כזרמים ריאקטיביים שניתנים לצפייה.

מה יוצרים: שילובים מבוססי-Coroutine שאוספים את ה-Flows האלה ומעדכנים את ממשק המשתמש של Android בהתאם.

השעיית פונקציות

הרבה שיטות שירות של SDK הן פונקציות השעיה של Kotlin, כלומר אפשר לקרוא להן מתוך היקף של שגרת המשך (coroutine) והן יושעו (לא ייחסמו) בזמן ההמתנה לתוצאה.

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)
    }
}

תהליכי עבודה בשירות Chat

‫chatService exposes Kotlin SharedFlow properties for observing real-time chat events. אוספים אותם בהיקף של קורוטינה עם מודעות למחזור החיים (lifecycleScope או viewModelScope).

Flow פליטות
messagesReceived רשימות של הודעות צ'אט נכנסות (מסוכנים, מנציגים וירטואליים או מהמערכת).
typingEvent כשמשתמש או נציג מקלידים.
memberEvent כשמשתמש או נציג מצטרפים לשיחה או עוזבים אותה (כולל העברות של נציגים וניתוקים).
stateChanged שינויים במצב של ספק הצ'אט (חיבור, מחובר, ניתוק, שגיאה).
chatReceived נתוני תשובות בצ'אט כשמתקבלת או מתעדכנת שיחה (כולל שינויים בסטטוס, הקצאת נציג ומצב אחרי סיום השיחה).

ה-chatService חושף תהליכי עבודה נוספים לאירועי העברה (transferEventSubject), לתוצאות של בדיקת סטטוס (checkStatusSubject) ולתוצאות של שליחת הודעות (messageSendResultSubject).

דוגמה להטמעה

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)
    }
}

מצב שיתוף המסך

מה ה-SDK מספק: מצב ההפעלה של שיתוף המסך מועבר דרך הקריאה החוזרת (callback) ‏onSessionStateChanged ב-ScreenShareCallbacks, שמועבר אל ScreenShareManager.startSession() – ראו Headless Mobile SDK for Android: Voice, screen share, scheduled call, and email. הקריאה החוזרת מופעלת בכל פעם שיש מעבר בין הסשנים INACTIVE, PENDING ו-ACTIVE.

מה יוצרים: פונקציית handler שמחוברת ל-ScreenShareCallbacks.onSessionStateChanged ומעדכנת את ממשק המשתמש כשמצב הסשן משתנה, בנוסף לנכס מקומי (לדוגמה, currentScreenShareSessionState) אם צריך לקרוא את המצב הנוכחי מחוץ לקריאה החוזרת.

דוגמה להטמעה

בנוסף לנתיב ScreenShareCallbacks, ‏ ScreenShareManager חושף גם ממשקי API עצמאיים של addStateChangeListener / removeStateChangeListener ופונקציית גישה סינכרונית getSessionState(). משתמשים בהם כשצריך לצפות במצב מרכיב אחר מזה שקרא ל-startSession(), או כדי לבצע קריאה חד-פעמית לא תגובתית. בניגוד לScreenShareCallbacks נתיב שבו ההחזרה מתבצעת אוטומטית כשהסשן מסתיים, מאזינים עצמאיים לא משוחררים אוטומטית. כדי למנוע דליפות, צריך לקרוא ל-removeStateChangeListener(...) בפירוק של הבעלים של מחזור החיים.

// 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)

נתונים בהתאמה אישית

מה ה-SDK מספק: מנגנון לצירוף מטא-נתונים מובְנים לסשנים של צ'אט, בצורה של מטען ייעודי (payload) חתום של JWT (מאומת על ידי הקצה העורפי) או בצורה של מטען ייעודי (payload) לא חתום (מוגדר ישירות בלקוח).

מה אתם יוצרים: את הלוגיקה של איסוף הנתונים באפליקציה, ובמקרה של נתונים חתומים, את נקודת הקצה של חתימת ה-JWT בשרת העורפי (במאמר Headless Mobile SDK ל-Android: תחילת העבודה מפורטים סכימת האימות המלאה ומטען ה-JWT).

נתונים מותאמים אישית חתומים

נתונים מותאמים אישית חתומים משתמשים באסימון JWT שחתום באמצעות הסוד של החברה בשרת העורפי. זו הגישה המאובטחת להעברת נתונים רגישים או מהימנים.

דוגמה להטמעה

יוצרים אובייקט נתונים מותאם אישית חתום מ-JWT שנוצר בקצה העורפי.

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

נתונים מותאמים אישית לא חתומים

נתונים מותאמים אישית לא חתומים מוגדרים ישירות בלקוח באמצעות CustomDataPayload ו-CustomDataItem.

דוגמה להטמעה

יצירת מטען ייעודי (payload) לא חתום עם פריטי נתונים גלויים ומוסתרים. ‫CustomDataPayload מקבל MutableMap<String, CustomDataItem?>, לכן צריך להשתמש בהקצאת אינדקס (או ב-mutableMapOf) כדי להוסיף כל פריט לפי מפתח. המקש שתבחרו (למשל, "user_name" או "account_tier") יהפוך לשם השדה שיוצג לנציג ולניתוח הנתונים או להעברת השיחה.

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)

פריטי נתונים בהתאמה אישית תומכים במאפיינים הבאים:

  • ‫label: התווית שמוצגת לסוכן.

  • ‫value: ערך הנתונים (דוגמאות: String, ‏ Int, ‏ Bool).

  • ‫type: סוג הנתונים (STRING, ‏ NUMBER, ‏ DATE, ‏ URL, ‏ BOOLEAN).

  • invisibleToAgent: אם true, הנתונים מוסתרים מהנציגים אבל זמינים לניתוב ולניתוח.

שימוש בנתונים מותאמים אישית בבקשות לצ'אט

צירוף נתונים מותאמים אישית כשמתחילים סשן צ'אט.

דוגמה להטמעה

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}")
    }
}

העברה של צ'אט עם גורמים מחוץ לדומיין

מה ה-SDK מספק: מבנה נתונים (ExternalChatTransfer) להעברת השם של סוכן קודם ותמליל השיחה אל סשן חדש ב-CCAI Platform.

מה יוצרים: תהליך ההעברה שמחלץ את ההקשר ממערכת הצ'אט החיצונית ואורז אותו עבור ה-SDK.

דוגמה להטמעה

הקוד הזה יוצר אובייקט להעברת צ'אט חיצוני עם השם של הנציג הקודם ומצרף אותו לנתונים בהתאמה אישית של הסשן.

// 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)

האפשרות הזו שימושית כשמעבירים משתמש ממערכת צ'אט של צד שלישי לסשן שמבוסס על CCAI Platform, וכך שומרים על ההקשר של השיחה עבור הנציג שמקבל את השיחה.

שיקולים לגבי שרשורים

ה-SDK לנייד ללא ממשק משתמש (headless) ל-Android מיועד לעבודה עם קורוטינות של Kotlin, והוא ברובו בטוח לשימוש בשרשור הראשי. עם זאת, יש שיקולים חשובים לגבי שרשורים בשילובים ללא ממשק משתמש.

בטיחות ה-thread הראשי

כל פריטי ה-Method ב-SDK הם thread-safe ואפשר לקרוא להם מכל שרשור. רוב הקריאות לשירותי SDK (לדוגמה, chatService?.start(), ‏ queueMenuService?.get()) הן פונקציות השעיה שמטפלות בשרשור שלהן באופן פנימי – הן משעות את שגרת ההמשך שקוראת להן בלי לחסום את השרשור. אפשר לקרוא להם בבטחה מהשרשור הראשי בתוך היקף של קורוטינה:

// 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 ושרשורים ברקע

השיטה ccaiShouldAuthenticate() ב-CCAIDelegate היא פונקציית השהיה, כך שאפשר לבצע בתוכה קריאות רשת בצורה בטוחה:

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
    }
}

עדכוני ממשק משתמש מהתקשרות חוזרת (callback) של SDK

כשעוקבים אחרי אירועי SDK באמצעות Flow, צריך לוודא שעדכוני ממשק המשתמש מתבצעים בשרשור הראשי. אם אתם משתמשים ב-lifecycleScope, התהליך הזה מתבצע אוטומטית:

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

אם אתם אוספים נתונים מ-dispatcher שאינו הראשי, אתם צריכים לחזור באופן מפורש:

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

חלוקת האתחול לשרשורים

‫CCAI.initialize() וקריאות קשורות לאתחול מודולים, כמו initializeChat(),‏ initializeCall() ו-initializeScreenShare(), חייבות להתבצע בthread הראשי ב-Application.onCreate(). אלה קריאות סינכרוניות שמגדירות את המצב הפנימי של ה-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)
    }
}

ניהול זיכרון

ניהול זיכרון תקין חיוני כדי למנוע דליפות ולהבטיח פירוק נקי של משאבי ה-SDK.

מחזור החיים של שגרות משנה (Coroutine) ו-Flow

תמיד מבטלים הפניות של שגרות המשך (coroutine) Job ו-Flow collectors בשיטות של מחזור החיים ב-Android (onDestroy() עבור Activities, ‏ onCleared() עבור ViewModels). אם לא עושים את זה, ההפניות יכולות להישאר פעילות יותר זמן מהמתוכנן, ולגרום לדליפות זיכרון.

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()
    }
}

הפניות למאזין ולקריאה חוזרת

כדי למנוע מחזורי שמירה, כדאי להשתמש בהפניות חלשות למאזינים ולקריאות חוזרות. כשפעילות או ViewModel מושמדים, צריך לנקות את כל הקריאות החוזרות הרשומות.

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()
    }
}

שיטות מומלצות

  • אחסון הפניות של קורוטינה Job: תמיד צריך לאחסן הפניות של Job שמוחזרות על ידי launch {} כדי למנוע קורוטינות שיוצאות משליטה וממשיכות לפעול אחרי שההיקף שלהן מסתיים.

  • ניקוי בשיטות של מחזור החיים: ביטול כל העבודות ומחיקת הקריאות החוזרות ב-onDestroy() (Activities) או ב-onCleared() (ViewModels).

  • שימוש בהפניות חלשות למאזינים: צריך להיזהר עם ביטויי למדה שתופסים באופן מרומז את this (ההקשר של Activity או Fragment שמקיף אותם), כי זה עלול למנוע מנגנון איסוף.

  • השירותים מנוהלים על ידי ה-SDK: אין צורך לפרסם שירותי SDK באופן ידני – הם מנוהלים על ידי אובייקט הסינגלטון CCAI.

פתרון בעיות

בקטע הזה מפורטות בעיות נפוצות בשילוב ופתרונות לבעיות האלה.

בעיות ב-build ובתלות

בקטע הזה מפורטות בעיות נפוצות שקשורות לבנייה ולתלות.

בעיה

‫Gradle לא יכול לפתור את com.ccaiplatform.android:CCAIKit.

  • הסיבה: מאגר ה-CCAI Maven חסר או שכתובת ה-URL שלו שגויה.

  • פתרון: מוודאים שsettings.gradle.kts כולל את הפרטים הבאים:

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

    שימו לב ל/ccaip/ קטע הנתיב. כתובת ה-URL הקודמת (https://sdk.ujet.co/android/) לא תפתור את הבעיות שקשורות לארטיפקטים.

בעיה

שגיאות קומפילציה שנדרשות ב-Java 17.

  • הגורם: נדרשת תאימות ל-Java 17.

  • פתרון: צריך לעדכן את build.gradle.kts:

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

בעיה

‫chatLibraryNotFound (קוד שגיאה 1100) בזמן הריצה.

  • הסיבה: המודול CCAIChatRed חסר בתלויות.

  • פתרון: נדרשים גם CCAIChat וגם CCAIChatRed כדי להשתמש ב-Chat. הוספה:

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

בעיות באתחול

בקטע הזה מפורטות בעיות נפוצות שקשורות לאתחול.

בעיה

NullPointerException כשניגשים אל CCAI.chatService.

  • הסיבה: לא בוצעה קריאה אל CCAI.initializeChat(), או שהקריאה בוצעה לפני CCAI.initialize().

  • תיקון: מוודאים את סדר האתחול ב-Application.onCreate():

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

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

    3. ‫CCAI.initializeCall(context) (אם משתמשים בשיחות קוליות או בשיחות מתוזמנות)

    4. CCAI.initializeScreenShare(context, options) (אם משתמשים בשיתוף מסך)

בעיה

שירותי ה-SDK מחזירים null אחרי האתחול.

  • הסיבה: לא בוצעה קריאה ל-CCAI.initialize() בשרשור הראשי, או שהמחלקה Application לא רשומה במניפסט.

  • תיקון: מוודאים שהשורה android:name=".MainApplication" מופיעה בקובץ AndroidManifest.xml וכל קריאות ה-init נמצאות ב-onCreate().

בעיות באימות

בקטע הזה מפורטות בעיות נפוצות שקשורות לאימות.

בעיה

האימות נכשל בלי התראה או שהפונקציה ccaiShouldAuthenticate() אף פעם לא מופעלת.

  • הסיבה: הערך CCAIDelegate לא הוגדר ב-InitOptions.

  • תיקון: מוודאים שהערך delegate מועבר אל InitOptions:

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

בעיה

שגיאות באימות JWT (קוד שגיאה 101).

  • הסיבה: פג התוקף של ה-JWT, הוא לא בפורמט הנכון או שהוא חתום עם סוד שגוי.

  • תיקון: מוודאים שהקצה העורפי:

    • השתמשתם בקוד הסודי הנכון של החברה.

    • הגדרת חותמות זמן תקינות של iat ו-exp.

    • הפונקציה מחזירה מחרוזת JWT בפורמט תקין.

בעיות בהתראות

בקטע הזה מפורטות בעיות נפוצות שקשורות להתראות פוש.

בעיה

התראות פוש לא מתקבלות או לא מטופלות.

  • הסיבה: טוקן ה-FCM לא רשום ב-SDK, שירות ההודעות של Firebase לא רשום או שמטעני נתונים נכנסים לא מועברים ל-SDK.

  • פתרון:

    • הפעלת CCAI.pushNotificationService?.updatePushToken(token) גם בonNewToken() וגם בזמן ההפעלה.

    • העברה של remoteMessage.data אל CCAI.pushNotificationService?.handlePushNotification(remoteMessage.data) משירות ההודעות של Firebase.

    • מוודאים שהקובץ google-services.json נמצא במודול של האפליקציה.

    • בודקים שמפתח חשבון השירות של Firebase מוגדר בפורטל האדמין של CCAI Platform.

    • אתם מציגים את ממשק המשתמש של ההתראות הגלויות או של השיחות הנכנסות בקוד האפליקציה שלכם.

בעיה

ה-SDK לא מגיב להתראות פוש נכנסות (הודעות מוזנחות).

  • הגורם: העברת סוג הנתונים הלא נכון לשיטה handlePushNotification().

  • הפתרון: מוודאים שמעבירים את remoteMessage.data (שהוא Map<String, String>), ולא את אובייקט RemoteMessage כולו.

בעיות בשיחת צ'אט

בקטע הזה מפורטות בעיות נפוצות בסשן צ'אט.

בעיה

‫chatService?.start() throws an exception or returns null.

  • הסיבה: menuId לא תקין, כשל באימות או בעיה ברשת.

  • פתרון:

    • מוודאים שהערך menuId תואם לתור תקף בפורטל האדמינים.

    • בודקים ש-ccaiShouldAuthenticate() מחליף JWT חתום ותקין דרך CCAI.authService?.authenticate(jwt) ומחזיר את טוקן האימות שנוצר.

    • מוודאים שהמכשיר מחובר לרשת.

    • עוטפים את הקריאה ב-try או ב-catch, ואז מתעדים את פרטי החריגה.

בעיה

הודעות ב-Chat לא מופיעות בממשק המשתמש.

  • הסיבה: לא מתבצעת צפייה בזרם ההודעות של שירות הצ'אט.

  • פתרון: מוודאים שאתם אוספים נתונים מה-Flow של שירות הצ'אט בהיקף שמתעדכן בהתאם למחזור החיים (ראו את הקטע Flows של שירות הצ'אט למעלה).

בעיות בשיתוף המסך

בקטע הזה מפורטות בעיות נפוצות בשיתוף מסך.

בעיה

שיתוף המסך לא מתחיל אחרי שהמשתמש מאשר את ההסכמה.

  • הסיבה: מודול CCAIScreenShare לא אותחל או ש-Android MediaProjection לא הופעל.

  • פתרון:

    • מוודאים שקוראים ל-CCAI.initializeScreenShare() עם ScreenShareOptions תקין.

    • מוודאים שמתחילים את MediaProjectionManager ואת שירות שפועל בחזית של Android בצורה נכונה אחרי קבלת ההסכמה.

    • ב-Android 14 ואילך, מוודאים שסוג השירות שפועל בחזית כולל את mediaProjection.

טיפים כלליים לניפוי באגים

  • כדי לראות את המצב הפנימי של ה-SDK, מומלץ להפעיל רישום מפורט ביומן במהלך הפיתוח.

  • בודקים את פורטל האדמין של CCAI Platform כדי לוודא שהגדרות התור נכונות, שהערוץ מופעל וששעות הפעילות מוגדרות.

  • כדי לוודא שמתבצעות קריאות ל-API אל *.ccaiplatform.com, אפשר לעקוב אחרי תנועת הרשת באמצעות כלים כמו Charles Proxy או Network Inspector ב-Android Studio.

  • מוודאים שגרסאות המודולים תואמות – כל המודולים של com.ccaiplatform.android צריכים להשתמש באותו מספר גרסה כדי למנוע בעיות תאימות.

סיכום הסדרה

ברכות – סיימתם את הסדרה בת חמישה החלקים בנושא Headless Mobile SDK ל-Android. השילוב שלכם כולל עכשיו:

כדי לעקוב אחרי העדכונים ב-SDK, כדאי להוסיף לסימניות את הפניית ה-API של Headless Mobile SDK – Android ולעיין בנתוני הגרסה של CCAI Platform.