@Composable
fun MessageItem(message: Message) {
val isGenerating = message.extraData["generating"] == true
val isFromAI = message.extraData["ai_generated"] == true
if (isFromAI) {
StreamingText(
text = message.text,
animate = isGenerating
)
} else {
Text(text = message.text)
}
}Compose Integration
The AI components work seamlessly with the Stream Chat Android SDK.
StreamingText
You can easily integrate the message streaming view into your Compose chat UI.
One option is to use it when rendering messages:
You can also add thinking indicators, for example at the bottom of the message list:
@Composable
fun ChatScreen(channelId: String) {
val chatClient = ChatClient.instance()
val channel = chatClient.channel(channelId)
val context = LocalContext.current
val factory = MessagesViewModelFactory(
context = context,
channelId = channelId,
)
val viewModel = viewModel(
modelClass = MessageListViewModel::class.java,
factory = factory,
)
var assistantState by remember { mutableStateOf<String?>(null) }
// Listen to AI typing indicator events
LaunchedEffect(channelId) {
channel.subscribeFor<AIIndicatorUpdatedEvent> { event ->
assistantState = event.aiState
}
}
Column {
// Message list
MessageList(viewModel)
// Typing indicator overlay
if (assistantState != null) {
AITypingIndicator(
label = {
Text(
when (assistantState) {
"AI_STATE_THINKING" -> "Thinking"
"AI_STATE_CHECKING_SOURCES" -> "Checking sources"
"AI_STATE_GENERATING" -> "Generating response"
else -> ""
}
)
}
)
}
// Composer
ChatComposer(
onSendClick = { messageData ->
channel.sendMessage(
Message(
text = messageData.text,
attachments = messageData.attachments.map { uri ->
// Convert URI to attachment
}
)
).enqueue()
},
onStopClick = {
channel.sendEvent(EventType.AI_TYPING_INDICATOR_STOP).enqueue()
},
isStreaming = assistantState == "AI_STATE_GENERATING",
)
}
}For a reference implementation showing how to integrate these components with Stream Chat’s client API, please check our sample app.