Troubleshooting
A guide to diagnosing and resolving typical issues during integration and operation of the edna Chat Center Android SDK.
Before diagnosing, enable detailed logging: ChatLoggerConfig(logLevel = ChatLogLevel.VERBOSE). Logcat will then show initialization, authorization, message-send, and WebSocket events. Configuration and LogInterceptor — Logger.
SDK does not initialize
Symptoms
- The app crashes when calling
init() IllegalStateExceptionwhen callingauthorize(),send(), or other SDK methods without a priorinit()
Checklist
-
Is
init()called inApplication.onCreate()?The correct place for the constructor and
init():class MyApp : Application() {
lateinit var chatCenterUI: ChatCenterUI
override fun onCreate() {
super.onCreate()
chatCenterUI = ChatCenterUI(applicationContext).apply {
init(providerUid = "YOUR_PROVIDER_UID", config = chatConfig)
}
}
}If the constructor is called from an Activity — the SDK throws
IllegalArgumentException:class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
ChatCenterUI(this) // Activity context — throws IllegalArgumentException
}
} -
Is
applicationContextpassed (not an Activity context)?ChatCenterUIrequires an Application-level context- When an Activity context is passed, the SDK throws
IllegalArgumentExceptionwith the message "pass an Application-level Context"
-
Is
providerUidset correctly?- The value is provided by edna during integration
- An empty string leads to
IllegalArgumentException
-
Protection against double initialization
- A repeated
init()call re-initializes the SDK with a cleanup of the current state and re-creation of the transport layer. - If you store the instance in
Applicationvialateinit var, check initialization before re-assigning:if (!::chatCenterUI.isInitialized) {
chatCenterUI = ChatCenterUI(applicationContext).apply { init(...) }
}
- A repeated
How to verify
- In Logcat (filter
ELog, see Collecting logs) after a successfulinit(), records about SDK initialization and bringing up the network transport appear. - The next
authorize()call does not throwIllegalStateException.
The chat does not load
Symptoms
getChatFragment()returnsnull- The chat screen shows the loading indicator indefinitely
- Connection error
Checklist
-
Is the user authorized?
getChatFragment()returns non-nullonly afterauthorize().chatCenterUI.authorize(chatUser, chatAuth)
val fragment = chatCenterUI.getChatFragment() -
Are the server URLs correct?
cloudHostin the simplified constructor accepts a full URL with a protocol — without a path:val transport = ChatTransportConfig(cloudHost = "https://company.edna.io")The SDK passes the
cloudHostvalue directly intoRetrofit.baseUrl()andOkHttp Request.url()— without the protocol you getIllegalArgumentException: Expected URL schemeduring initialization. A URL with a path (e.g.,"https://company.edna.io/api") produces an incorrect final address. If you need a non-standard port or path, use the full form with explicitrest,webSocket,dataStore(ChatTransportConfig). -
Does the device have network access?
- Open
https://<your-cloud-host>/in the device's browser — expect HTTP 200/301/404 (any response from the server, not "connection refused") - With a VPN, make sure traffic is routed correctly: the SDK uses WSS (port 443) and HTTPS
- In VERBOSE logs, the SDK shows the final WebSocket and REST URLs after normalization
- Open
-
Does ProGuard/R8 not obfuscate the SDK?
warningWithout
-keeprules in a release build, the SDK is guaranteed to crash on the first config load or message receive (JsonSyntaxException,ClassNotFoundException). The full set of rules for the SDK and its transitive dependencies is in Installation → Obfuscation; the rules must be in the app'sproguard-rules.pro.
How to verify
- In Logcat after a successful
authorize()and chat opening, there are no exceptions, and records about receiving messages via WebSocket are visible. chatCenterUI.getChatFragment()returns a non-nullFragment.
Messages do not send
send() returns false, the message hangs in "sending" status or shows an error icon. In descending probability:
send()threwIllegalStateException— the SDK is not initialized, callinit()first.send()returnedfalse— the user is not authorized:authorize()was not called or returned an error.- The WebSocket is disconnected — subscribe to
ChatCenterUIListener.networkErrorReceived(error); on disconnect, WARNING/ERROR records appear in Logcat. - The file exceeds the server limit —
AttachmentSettings.content.maxSize(in megabytes). A typical value is 30; for on-premise it differs. See Files and media do not send.
On a successful send, the status goes through the chain SENDING → SENT → DELIVERED → READ. The full set of MessageStatus values (including ENQUEUED, FAILED) is in the canonical section.
Files and media do not send
Symptoms
- The attach button does not open the file picker
- When picking a file, an "file too large" or "file type not supported" error is shown
- The file is picked, but the send fails with an error
- The image preview does not load
Checklist
-
Are media read permissions granted?
- The SDK shows the system request dialog itself when trying to attach a file: for CAMERA and storage — via its own service permission-request screen; for
RECORD_AUDIO— via the standardActivityResultContracts.RequestPermission()(see the Voice messages section) - On "Don't ask again" refusal for CAMERA/storage, the SDK shows its own
AlertDialogwith a button to go to the app's system settings. For the microphone, the SDK shows only a Toast — the navigation to settings must be implemented in your own UI. - Runtime permissions and their behavior across Android versions — Android permissions
- The SDK shows the system request dialog itself when trying to attach a file: for CAMERA and storage — via its own service permission-request screen; for
-
Does the file size not exceed the server limit?
- The limit arrives in
AttachmentSettings.content.maxSize(in megabytes) from the server when the config is loaded - A typical value is 30 MB, but for on-premise installations it may differ. Check with your edna administrator
- Pre-filter large files on the app side — the error status from the server arrives asynchronously
- The limit arrives in
-
Is the file MIME type allowed?
- Allowed extensions arrive in
AttachmentSettings.content.fileExtensions - The server configuration may forbid
.exe,.dll,.apk, and other potentially dangerous types - Logcat shows an error specifying the rejected extension
- Allowed extensions arrive in
-
Is camera access available (if photo capture is used)?
- The SDK requests the
CAMERApermission via the system dialog when trying to open the camera from the chat - If the permission is revoked — the camera button will be unavailable
- The SDK requests the
-
Is the file passed through
ContentResolver, not directly viaFile?- If your code works with
java.io.Fileover a Scoped Storagecontent://URI before passing the file to the SDK, the send will fail. UseContentResolveror pass URIs to the SDK API without your own pipeline.
- If your code works with
How to verify
- A test file below
maxSizewith an allowed extension is sent and transitions to statusSENT. - In Logcat, on a server refusal, the reason is visible: "File size exceeds limit", "Disallowed file type", or similar.
Voice messages do not record or play
Symptoms
- The voice-message record button does not respond to a long-press
- Recording starts, but on release, sending does not happen
- A received voice message does not play — no sound or a player error
- In Logcat —
MediaRecorder start failed/IllegalStateException
Checklist
-
Is the
RECORD_AUDIOpermission granted?- The SDK requests the permission via the standard
ActivityResultContracts.RequestPermission()when tapping the microphone button in the chat - If the user refuses, the button visually remains, but recording does not start
- Check
Settings → Apps → <your app> → Permissions → Microphone = Allow
- The SDK requests the permission via the standard
-
Is the microphone not occupied by another app?
- A concurrent call, VoIP session, or active voice-recorder app locks the microphone. Close the competing app and try again.
-
Does the server's
fileExtensionsconfiguration allow the recording format?- The SDK records voice in
.ogg(OPUS) on Android 10+ (API 29+) and in.3gp(AMR-WB) on Android 9 and below - If the server configuration forbids these extensions, voice send will not pass
- Check with the edna administrator that
oggand3gpare present inAttachmentSettings.content.fileExtensions
- The SDK records voice in
-
Does the recording duration not exceed
maxSize?- The recording file size obeys the same server limit
AttachmentSettings.content.maxSize(in megabytes) - A long recording (several minutes) may exceed it — the server rejects the send post hoc
- The recording file size obeys the same server limit
-
Playback: volume and Do Not Disturb
- The SDK plays voice messages on the media sound channel — adjust the media-channel volume, not the ringer
- Do Not Disturb mode set to "Total silence" mutes playback. Check DND exceptions for media
How to verify
- A long-press on the microphone button with
RECORD_AUDIOgranted starts recording and shows a signal-level indicator. - Releasing after a pause longer than 1 second sends the file; the message status transitions
SENDING → SENT. - Playing a received voice message plays audio without errors in Logcat.
Push notifications do not arrive
Symptoms
- Notifications are not shown when the app is in the background
- Notifications of other SDKs work, but edna does not
Checklist
The checks go from cheap build-time to runtime — work through the list top-down.
-
Is
google-services.jsonin the right place?- The file must be in the root of the app module (next to
build.gradle), not next toAndroidManifest.xml - The
com.google.gms.google-servicesplugin is connected inbuild.gradle(app) — without it, the file is ignored
- The file must be in the root of the app module (next to
-
Is the same Firebase project connected in the edna admin panel? In the edna Chat Center admin panel, on the "Notifications" tab of the Android channel, Sender ID and service-account.json are specified. Both values must belong to the same Firebase project whose
google-services.jsonis in the app. If the fields in the admin panel are empty or specify a different project, the edna backend cannot send push, even if the SDK correctly registered the token (a test push from Firebase Console will still arrive). Verify the values: Connecting the Android mobile chat. -
Is
FirebaseMessagingServiceregistered in the manifest?<!-- Replace .YourFcmService with the full name of your service -->
<service android:name=".YourFcmService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service> -
Is the FCM token passed to the SDK?
// In your FirebaseMessagingService:
override fun onNewToken(token: String) {
// setFCMToken — a static method in the ChatCenterUI companion object
ChatCenterUI.setFCMToken(token, applicationContext)
} -
Is
handleFCMMessage()called?// In your FirebaseMessagingService:
override fun onMessageReceived(message: com.google.firebase.messaging.RemoteMessage) {
// The SDK filters push by the field origin == "threads"; if the push is not from edna,
// the call is ignored (a corresponding record appears in VERBOSE logs)
chatCenterUI.handleFCMMessage(message.data)
} -
handleFCMMessagebehavior relative toinit()state- With the synchronous form of
init(): thehandleFCMMessage()call waits for the current initialization to complete and then processes the message normally. Ifinit()was never called —handleFCMMessage()throwsIllegalStateException. - With
initAsync(): the call is safe, the push is placed into an internal queue and processed after initialization completes.
- With the synchronous form of
-
Is
origin=threadspresent in the payload? If the push reaches the device buthandleFCMMessagedoes nothing — check theoriginfield inmessage.data. When sent from the edna admin panel, it is embedded by the server push template; when sent manually via Firebase Console / AppGallery Connect,origin=threadsmust be added manually to Custom data. Template description: Push notification template setup.
How to verify
- In Logcat after
onNewToken(...), a record about saving the FCM token is visible. - On a test push (via Firebase console or backend),
onMessageReceivedis called, and a new message appears in the UI/Logcat.
Push notifications do not arrive (HMS / Huawei)
On Huawei devices without Google Play Services, Huawei Push Kit (HMS) is used, not FCM. Symptoms and diagnostic steps differ from Google. Basic setup (dependencies, manifest, token handler) is in Push notification setup → HMS; below — integration diagnostics.
Symptoms
- A build with HMS on a Huawei device does not show notifications, while the FCM build works on other phones.
- In
ChatLogLevel.VERBOSElogs, the HCM token is empty or not passed.
Checklist
-
Is the HCM token passed via the static method?
// setHCMToken — a static method in the ChatCenterUI companion object
override fun onNewToken(token: String) {
ChatCenterUI.setHCMToken(token, applicationContext)
}warningDo not confuse
setFCMTokenandsetHCMToken— these are different channels. The SDK stores the token by type, and an incorrect call silently does nothing: push will not arrive. -
Is
agconnect-services.jsonin the root of the app module?- The file is required by the HMS Gradle plugin to register the Service-ID. Without it, HMS is not activated.
-
Is the HMS service (a subclass of
HmsMessageService) registered in the manifest with the intent filtercom.huawei.push.action.MESSAGING_EVENT? -
Is the HMS
RemoteMessage.datadecoded as base64 → JSON → Bundle, not given to the SDK as-is? Unlike FCM, HMS delivers the payload inRemoteMessage.dataas a base64 string with JSON inside. Before callinghandlePushMessage(bundle), the string must be decoded and unpacked into aBundle— otherwise the SDK sees "origin=threads" as an invalid payload and ignores the push. Decoder example: Push notification setup → HMS, Step 2.noteThe import is critical: both FCM and HMS have a
RemoteMessageclass, but they are different. A mixed-up import breaks the build or causesNoSuchMethodErrorat runtime. -
Does App_package in the admin panel match the module's
applicationId? In the edna Chat Center, on the "Parameters" tab of the Huawei channel, App_package is specified — this is theapplicationIdfrombuild.gradle(.kts). If the values diverge (a typical mistake after renaming the package or when using a separate Huawei build withapplicationIdSuffix), the edna backend will not send push to your app. Verify the value: Connecting the Huawei mobile chat → Channel parameters. -
Are the Huawei App ID and App Secret in the admin panel up to date? After OAuth 2.0 keys are re-created in AppGallery Connect, old values in the admin panel stop working silently — with no explicit error in logs. Verify Huawei App ID and Huawei App Secret in the channel against the current OAuth 2.0 Client ID and Client secret from AppGallery Connect → Project settings → App information.
How to verify
- In Logcat after
onNewToken(...), a record about saving the HCM token appears. - On a test push via AppGallery Push Kit, the message reaches
onMessageReceivedand then the SDK's UI/Logcat.
The theme is not applied
Symptoms
- Colors/fonts do not match the specified ones
- Dark theme does not switch
Checklist
-
Is the theme set before
init()?// Recommended order: theme → init()
chatCenterUI.theme = myTheme
chatCenterUI.darkTheme = myDarkTheme
chatCenterUI.init(providerUid, config)Setting the theme after
init()is supported, but changes take effect on the next chat screen opening. If the changes are not visible — restart the chat. -
Is the customization done via
ChatComponents? The only public way to configure the theme is to passChatComponentsinto theChatThemeconstructor. Colors, fonts, and images are set on the component properties:val components = ChatComponents(applicationContext).apply {
chatMainComponent.chatBackgroundColor = R.color.my_bg
// configuration of other components...
}
val myTheme = ChatTheme(components)
chatCenterUI.theme = myThemeThe configurable component fields are listed in Design system → Components.
-
Is the dark theme set as a separate
ChatComponentsinstance?- If
darkThemeis not set, the SDK uses thethemevalue in both modes - The light and dark themes must use different
ChatComponentsinstances — otherwise color changes in one mode overlap with the other
- If
How to verify
- Open the chat after
chatCenterUI.theme = .... The colors/fonts visually match the ones passed. - VERBOSE-level logs contain records about theme application when
ChatFragmentis created.
WebSocket disconnects
Symptoms
- Frequent reconnects
- Messages arrive with a delay
- In logs:
ChatCenterUIListener.networkErrorReceivedor WARNING/ERROR messages in the SDK logs
Checklist
-
Are timeouts not too small?
WSConfigaccepts values in seconds, default is 30. If the network is unstable, timeouts should be increased, not decreased. Too short intervals (5/5/5) lead to constant disconnects. For unstable networks, it is reasonable to raise to60/60/30:val wsConfig = WSConfig(connectionTimeout = 60, sendTimeout = 60, pingInterval = 30) -
Is
keepWebSocketActiveenabled when needed?- If you need the unread counter outside the chat screen — set
keepWebSocketActive = trueinChatConfig - Otherwise the WebSocket closes when leaving the chat screen
noteThe parameter is marked deprecated — the resulting behavior is controlled by the server configuration. See
ChatConfig→ Deprecated parameters. - If you need the unread counter outside the chat screen — set
-
Is automatic reconnect enabled?
- If the logs show frequent isolated disconnects, set
WSConfig.isReconnectEnabled = true; the SDK will restore the connection itself with exponential backoff and a "Connecting..." title in the chat toolbar. - If reconnect is already enabled but the connection still periodically drops without recovery — increase
maxReconnectAttemptsor check the stability of the device's network. - A full behavior description and pending-message behavior is in Network config → Automatic reconnect.
- If the logs show frequent isolated disconnects, set
-
Is SSL pinning configured correctly?
- The certificate specified in
ChatNetworkConfig(sslPinning = SSLPinningConfig(...))must match the server one - We recommend specifying at least 2 certificates (current + backup): on a rotation on the server side, the backup ensures uninterrupted operation for all clients
warningAn SSL pinning error makes communication with the server completely impossible and is not accompanied by an understandable UI message — only
networkErrorReceived(error)in the listener. Before publishing a release build, test pinning separately. - The certificate specified in
How to verify
- In
ChatCenterUIListener.networkErrorReceived(error), events arrive less than once per minute on a stable network.
Crashes in release builds
Symptoms
- Works in debug, crashes in release
ClassNotFoundExceptionorNoSuchMethodExceptionin the stack trace
Checklist
-
Are ProGuard/R8 rules added?
warningWithout
-keeprules, the release build crashes on the first config load or message receive. Check obfuscation first on any release crashes.-keeprules for the SDK and its transitive dependencies (Gson, Retrofit, OkHttp, Markwon, Jsoup) are collected in Installation → Obfuscation. Copy them into the app'sproguard-rules.proentirely. -
Is
mapping.txtsaved?- With R8 enabled, class names in the stack trace are obfuscated (
a.b.c.d). Withoutmapping.txtfrom the same build, it is impossible to restore a readable stack trace. - The file is generated at
app/build/outputs/mapping/release/mapping.txton every release build.
- With R8 enabled, class names in the stack trace are obfuscated (
-
Does the release build reproduce the problem locally?
- Build
assembleReleaselocally and install the APK on a device - Enable
ChatLogLevel.VERBOSE— the stack trace in Logcat will show which class was not found - Add the missing class to
-keeprules, rebuild
- Build
How to verify
- After adding the rules, the release build passes a smoke test: opening the chat, sending a message, receiving a message — without a
FATAL EXCEPTIONin Logcat. - In Crashlytics/Sentry, no new events with
ClassNotFoundException/NoSuchMethodException/JsonSyntaxExceptionfrom theedna.chatcenter.*packages appear within 24 hours after the release.
Push notifications arrive unpredictably
Some push messages reach the device, some are lost (four to seven out of ten); or delivery comes with a minute delay after a long background; or the first push after a device reboot is lost before the app is opened. Possible reasons:
-
Race with cold SDK startup. With synchronous
init(), an early push is blocked until initialization completes and is processed afterwards. If on a full cold start of Application the first push arrives beforeinit()and the app closes immediately after arrival — the push is lost. The solution isinitAsync(): the push is queued and delivered when the SDK is ready. -
Duplicates with the same
message_id. The SDK deduplicates push by message id; a repeated push with the same id is silently ignored. Deduplication decisions are visible in VERBOSE logs. -
OEM power-saving restrictions. Xiaomi MIUI, Huawei, Samsung, and others may apply their own policies (app whitelist, "protected apps", "sleeping apps"). In the help for end users, describe how to add your app to the whitelist.
In the background with priority: high, 90%+ of messages get through; with priority: normal, the push arrives on the next active app usage.
The user refused the permission (CAMERA / RECORD_AUDIO / Storage)
Symptoms
- The corresponding button in the chat does not respond to taps
- The system permission request dialog is no longer shown
- The chat logic works, but file attachment / voice recording / photo capture does not
Checklist
-
Did a preventive explanation dialog appear before the system request?
- If
permissionsDescriptionDialogsEnabled = trueis enabled inChatConfig, the SDK first shows its own dialog explaining why the permission is needed, and only then — the Android system dialog - If the flag is off, the system dialog appears immediately
- The style of the preventive dialog is configured via
PermissionDescriptionPopupStyle— see Design system
- If
-
After two refusals: does the SDK offer to go to app settings?
- After "Don't ask again", Android 11+ switches the permission to "Denied permanently" mode
- For CAMERA and storage: the SDK shows an
AlertDialogwith a "Settings" button, which opens the app screen (Settings.ACTION_APPLICATION_DETAILS_SETTINGS). The integrator does not need to do anything manually. - For
RECORD_AUDIO: the SDK shows only a Toast about the inability to record a message, without navigation to settings. In this case, prompt the user to go to settings in your own UI or handle the case through your own listener.
-
Is the permission not requested at all?
- The SDK requests the permission lazily — on the first attempt to use the corresponding feature (camera, microphone, files)
- If the request does not appear on the button tap — the corresponding feature is disabled in
ChatConfig(e.g., file attachment is forbidden by server settings)
-
POST_NOTIFICATIONSnot granted on Android 13+?- This only affects showing push notifications in the system shade, not push handling inside the SDK (
handleFCMMessage/handlePushMessagework independently) - The SDK requests this permission on the first chat opening on Android 13+. If the user refused — push will be processed by the SDK, but not displayed in the system shade. If desired, you can additionally request the permission in your own UI (for example, on user sign-in) before opening the chat.
- This only affects showing push notifications in the system shade, not push handling inside the SDK (
How to verify
- In
Settings → Apps → <app> → Permissions, the corresponding permission is in theAlloworAsk every timestate. - The attach button / microphone / camera in the chat opens the corresponding UI without delay.
- A table of all SDK runtime permissions and the scenarios in which they are requested is in Android permissions.
The unread counter does not update
Symptoms
- The badge shows a stale value
unreadMessageCountChanged()is not called
Checklist
-
Is the user authorized?
- The counter only works after a successful
authorize(). This is the most common cause — check it first.
- The counter only works after a successful
-
Is
ChatCenterUIListenerset?chatCenterUI.setChatCenterUIListener(object : ChatCenterUIListener {
override fun unreadMessageCountChanged(count: UInt) {
updateBadge(count.toInt())
}
})noteThe parameter type is
kotlin.UInt. To use from Java, you will need to obtain anintrepresentation via reflection or a wrapper method on Kotlin: direct override from Java is not possible due to name mangling. -
Is
keepWebSocketActive = trueif the chat is closed?- Without an active WebSocket, the counter is updated only when the chat is opened
- The parameter is marked deprecated, the resulting behavior is controlled by the server — see
ChatConfig
How to verify
- In Logcat, on receiving a new message,
unreadMessageCountChangedis called with an increasedcount. - On opening the chat and reading all messages,
unreadMessageCountChangedis called withcount = 0u.
Collecting logs for support
At the ChatLogLevel.VERBOSE level, the SDK logs, among other things, message texts, user identifiers, authorization metadata, and request URLs. Before sending logs to support@edna.io:
- Obtain user consent (or use a depersonalized test account) — this is a GDPR/152-FZ requirement.
- Remove authorization tokens (
Authorization: Bearer ..., JWT in payload) — they grant access to the user's session. - Mask conversation text or use a reproduction on synthetic data.
Do not send raw VERBOSE logs from a client's production device without legal coordination.
If the issue is not resolved, collect logs and send them to support@edna.io:
- Enable the maximum logging level:
ChatLoggerConfig(applicationContext, logLevel = ChatLogLevel.VERBOSE)(pass the config to theChatCenterUIconstructor). - Reproduce the issue.
- Export the logs: via the Shake gesture in the chat, via
adb logcat | grep ELog, or from files in{filesDir}/logs/. For sending from release builds without physical access to the device, useChatLoggerConfig.LogInterceptor(Crashlytics/Sentry/AppMetrica).
On Android 11+ (API 30+), the data/data/{package}/files/ directory is not accessible via ADB for release builds (debuggable = false). Use the Shake gesture, LogInterceptor, or sending logs from the app itself.
A detailed logger configuration, formats, and LogInterceptor examples — Logger.
Related sections
- SDK errors — full list of exceptions and their causes
- Logger — full configuration, log levels,
LogInterceptor - Installation → Obfuscation — full list of ProGuard/R8 rules
- Push notification setup — basic FCM and HMS integration
- Android permissions — full table of runtime permissions and request scenarios
- Design system → Components — SDK styling, including the permission request dialog
ChatTransportConfig— transport configuration with a non-standard port/path- Report a bug — bug-report template with required artifacts
If none of the checklists helped, contact support at support@edna.io. Before contacting, try to reproduce the issue on a minimal test project — if the problem does not reproduce there, this helps localize the defect in the integration rather than in the SDK.