This is documentation for Stream Chat Android SDK v6, which is no longer actively maintained. For up-to-date documentation, see the latest version (v7).

Troubleshooting

Common issues and solutions when integrating the Stream Chat Android SDK.

Initialization Issues

ChatClient Not Initialized

Symptom: IllegalStateException: ChatClient is not initialized

Cause: Attempting to use ChatClient.instance() before calling ChatClient.Builder(...).build().

Solution: Initialize ChatClient in your Application class:

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        val client = ChatClient.Builder(apiKey, this)
            .withPlugins(
                StreamOfflinePluginFactory(appContext = this),
                StreamStatePluginFactory(config = StatePluginConfig(), appContext = this),
            )
            .build()
    }
}

ProGuard/R8 Issues

Symptom: Crashes in release builds, missing classes, or serialization errors.

Cause: ProGuard/R8 obfuscation removing required classes.

Solution: The SDK includes consumer ProGuard rules automatically. See the consumer rules in the SDK repository for details. If you still encounter issues, ensure the consumer rules are being applied correctly by your build configuration.

Push Notification Issues

FCM Token Not Refreshing

Symptom: Push stops working after token refresh.

Solution: Use FirebaseMessagingDelegate to handle token registration and refresh:

class MyFirebaseMessagingService : FirebaseMessagingService() {
    override fun onNewToken(token: String) {
        FirebaseMessagingDelegate.registerFirebaseToken(
            token = token,
            providerName = "MyFirebaseProvider", // Must match dashboard config
        )
    }

    override fun onMessageReceived(message: RemoteMessage) {
        FirebaseMessagingDelegate.handleRemoteMessage(message)
    }
}

The snippet above (using FirebaseMessagingDelegate) is the recommended way to handle token refresh. If you have configured FirebasePushDeviceGenerator in your NotificationConfig, the SDK already registers the device automatically on connectUserdo not also call ChatClient.addDevice(...) from your application code.

If your application does not use FirebasePushDeviceGenerator and you manage the Firebase token flow yourself, you can register the device manually by calling ChatClient.addDevice(...) from your own FirebaseMessagingService.onNewToken(...). The call requires a connected user, so ensure ChatClient.connectUser(...) has succeeded first. If onNewToken fires before a user is connected, cache the token and register it after the next successful connectUser(...):

ChatClient.instance().addDevice(
    Device(
        token = token,
        pushProvider = PushProvider.FIREBASE,
        providerName = "MyFirebaseProvider",
    )
).enqueue { /* Handle result */ }

State & UI Issues

Messages Not Updating

Symptom: New messages don't appear or UI doesn't reflect changes.

Cause: The channel is not being watched. Messages only update in real-time for watched channels.

Solution: Ensure the channel is watched before expecting real-time updates:

// Watch the channel to receive real-time updates
ChatClient.instance().channel(channelType, channelId).watch().enqueue { result ->
    when (result) {
        is Result.Success -> { /* Channel is now watched */ }
        is Result.Failure -> { /* Handle error */ }
    }
}

For Compose, use the provided ViewModels which handle watching automatically:

val viewModelFactory = MessagesViewModelFactory(
    context = context,
    channelId = cid,
)
val messageListViewModel = viewModel<MessageListViewModel>(factory = viewModelFactory)

Channel List Not Refreshing

Symptom: Channel list shows stale data or doesn't update with new messages.

Cause: Event handling not configured or custom filter issues.

Solution:

  1. Verify you're using the SDK's ViewModel or observing state correctly
  2. Check your ChatEventHandler if using custom event handling (see Channels State and Filtering)
  3. Ensure the StatePlugin is configured

Compose Recomposition Issues

Symptom: Compose UI updates excessively or not at all.

Solution: Use the SDK's state hoisting pattern:

// Let the ViewModel handle state
val messagesState by messageListViewModel.currentMessagesState.collectAsStateWithLifecycle()

MessageList(
    currentState = messagesState,
    // ...
)

Avoid creating new objects in composition that would trigger recomposition.

Connection Issues

Frequent Disconnections

Symptom: WebSocket disconnects frequently, especially on mobile networks.

Cause: Network changes or aggressive keep-alive settings.

Solution: Handle connectivity changes:

ChatClient.instance().clientState.connectionState
    .onEach { state ->
        when (state) {
            is ConnectionState.Connected -> { /* Online */ }
            is ConnectionState.Connecting -> { /* Reconnecting */ }
            is ConnectionState.Offline -> { /* Handle offline */ }
        }
    }
    .launchIn(scope)

Token Expiry

Symptom: 401 Unauthorized errors or connection failures after some time.

Cause: JWT token expired and tokenProvider not configured.

Solution: Provide a token provider that fetches fresh tokens:

ChatClient.Builder(apiKey, context)
    .withPlugins(/* ... */)
    .build()

// Connect with token provider
ChatClient.instance().connectUser(
    user = user,
    tokenProvider = {
        // Fetch fresh token from your backend
        myBackend.getStreamToken(userId)
    }
).enqueue { result ->
    // Handle result
}

For more details on handling token expiry with push notifications, see the Push Notifications documentation.

UnknownHostException From the Token Provider in the Background

Symptom: A high volume of non-fatal UnknownHostException (for example "Unable to resolve host") coming from your TokenProvider.loadToken(), almost always while the app is backgrounded.

Cause: loadToken() is called on the initial connection and again when a fresh token is needed (a token expiry or authentication error, an unhealthy connection that is re-established, an unrecoverable socket error, or an explicit disconnect() / disconnectSocket()). A reconnection that needs a fresh token can run while the app is in the background, and on a poor or restricted network (Doze, Wi-Fi sleep, network handoff, or App Standby buckets) the DNS lookup for your backend fails. The SDK treats this as a failed connection attempt and retries, so it is not a crash.

There are two call sites:

  1. The SDK's own reconnection: SocketFactory.buildUrlTokenManager.ensureTokenLoadedloadToken.
  2. Your app calling connectUser(): ChatClient.connectUsersetUserloadToken. The SDK never calls the public connectUser() for you, so this path only runs when your code does.

Solution:

  • Make your TokenProvider resilient to a missing network. When there is no connectivity, fail fast and do not record the expected offline failure as a non-fatal in your crash reporter.
  • Gate your own connectUser() calls behind a foreground and connectivity check, so you do not start a connection while backgrounded.
  • You usually do not need to disconnect manually on background. The SDK already stops the socket when the app goes to the background and reconnects when it returns to the foreground. See Handling User Connection for the full lifecycle behavior.

Memory & Performance

Message List Performance

Symptom: Laggy scrolling in channels with many messages.

Solution:

  1. Use MessageLimitConfig to limit in-memory messages
  2. For Compose, the MessageList uses LazyColumn which virtualizes automatically
  3. For XML Views, ensure you're not blocking the main thread in custom view holders

Debugging Tips

Enable Logging

Enable detailed logging to diagnose issues:

ChatClient.Builder(apiKey, context)
    .logLevel(ChatLogLevel.ALL)
    .loggerHandler(AndroidStreamLogger())
    .build()

Filter logcat by tag Chat: to see SDK logs.

Inspect Network Requests

Use the ApiRequestsAnalyser to track API calls:

val analyser = ApiRequestsAnalyser.get()
analyser.dumpRequestByName("QueryChannels")

Check Connection State

Monitor connection state for debugging:

ChatClient.instance().clientState.connectionState.value
ChatClient.instance().clientState.user.value
ChatClient.instance().clientState.initializationState.value