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

Alternatively, you can manually register the device using addDevice:

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.

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