
Building a Real-Time Chat App with Stream's Android SDK, Jetpack Compose, and Offline AI Agents
A comprehensive tutorial on building a real-time chat app using Stream's Android SDK, Jetpack Compose, and offline AI agents for intelligent responses.
Introduction
This tutorial will guide you through building a real-time chat application for Android using Stream's Chat SDK, Jetpack Compose for the UI, and offline AI agents powered by TensorFlow Lite for intelligent responses when the network is unavailable. By the end, you'll have a fully functional chat app with real-time messaging, modern UI, and offline AI capabilities.
Table of Contents
- 1. Prerequisites
- 2. Project Setup
- 3. Integrating Stream Chat SDK
- 4. Building the Chat UI with Jetpack Compose
- 5. Adding Real-Time Messaging
- 6. Implementing Offline AI Agents
- 7. Testing the Application
- 8. Conclusion and Next Steps
- 9. Frequently Asked Questions
1. Prerequisites
- Android Studio Arctic Fox or later
- Kotlin 1.8+
- Basic understanding of Jetpack Compose
- Stream account (free tier available)
- Familiarity with coroutines and Flow
2. Project Setup
Create a new Android project with an Empty Compose Activity template. Then add the required dependencies in your build.gradle (Module level) file:
dependencies {
// Stream Chat SDK
implementation "io.getstream:stream-chat-android:5.0.0"
// Jetpack Compose
implementation "androidx.compose.ui:ui:1.5.0"
implementation "androidx.compose.material3:material3:1.1.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2"
// TensorFlow Lite for offline AI
implementation "org.tensorflow:tensorflow-lite:2.14.0"
implementation "org.tensorflow:tensorflow-lite-support:0.4.0"
// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
}
Add the internet permission to your AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
3. Integrating Stream Chat SDK
Initialize Stream Chat in your Application class:
class ChatApplication : Application() {
override fun onCreate() {
super.onCreate()
val apiKey = "YOUR_STREAM_API_KEY"
val chatClient = ChatClient.Builder(apiKey, this).build()
// Set the chat client as a singleton or use dependency injection
ChatClient.setInstance(chatClient)
}
}
Don't forget to register the Application class in the manifest:
<application
android:name=".ChatApplication"
... >
...
</application>
4. Building the Chat UI with Jetpack Compose
Create a composable for the chat screen. We'll use MessageList and MessageInput components.
@Composable
fun ChatScreen(
channelId: String,
viewModel: ChatViewModel = viewModel()
) {
val messages by viewModel.messages.collectAsStateWithLifecycle()
val connectionState by viewModel.connectionState.collectAsStateWithLifecycle()
Column(modifier = Modifier.fillMaxSize()) {
when (connectionState) {
ConnectionState.CONNECTED -> {
MessageList(
messages = messages,
modifier = Modifier.weight(1f)
)
MessageInput(
onSendMessage = { text ->
viewModel.sendMessage(text)
},
modifier = Modifier.fillMaxWidth()
)
}
else -> {
CircularProgressIndicator(modifier = Modifier.align(Alignment.CenterHorizontally))
}
}
}
}
Implement the MessageList composable:
@Composable
fun MessageList(
messages: List<Message>,
modifier: Modifier = Modifier
) {
LazyColumn(
modifier = modifier.padding(8.dp),
reverseLayout = true
) {
items(messages) { message ->
MessageItem(message = message)
}
}
}
And the MessageItem composable:
@Composable
fun MessageItem(message: Message) {
val isCurrentUser = message.user.id == ChatClient.getCurrentUser()?.id
val backgroundColor = if (isCurrentUser) Color.Blue else Color.Gray
val textColor = if (isCurrentUser) Color.White else Color.Black
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
horizontalArrangement = if (isCurrentUser) Arrangement.End else Arrangement.Start
) {
Surface(
color = backgroundColor,
shape = RoundedCornerShape(8.dp)
) {
Text(
text = message.text,
color = textColor,
modifier = Modifier.padding(12.dp)
)
}
}
}
5. Adding Real-Time Messaging
Create a ChatViewModel to handle messaging logic:
class ChatViewModel(
private val chatClient: ChatClient = ChatClient.instance()
) : ViewModel() {
private val _messages = MutableStateFlow<List<Message>>(emptyList())
val messages: StateFlow<List<Message>> = _messages.asStateFlow()
private val _connectionState = MutableStateFlow<ConnectionState>(ConnectionState.INITIAL)
val connectionState: StateFlow<ConnectionState> = _connectionState.asStateFlow()
init {
connectToChannel()
}
private fun connectToChannel() {
viewModelScope.launch {
val channel = chatClient.channel("messaging", "general")
channel.watch().await()
_connectionState.value = ConnectionState.CONNECTED
channel.messages.collect { messageList ->
_messages.value = messageList
}
}
}
fun sendMessage(text: String) {
viewModelScope.launch {
val channel = chatClient.channel("messaging", "general")
channel.sendMessage(Message(text = text)).await()
}
}
}
6. Implementing Offline AI Agents
For offline AI capabilities, we'll use TensorFlow Lite with a lightweight language model. First, add the model to your assets folder.
Create an AiAgent class:
class AiAgent(context: Context) {
private val interpreter: Interpreter
private val tokenizer: Tokenizer
init {
// Load the TensorFlow Lite model
val model = FileUtil.loadMappedFile(context, "ai_model.tflite")
interpreter = Interpreter(model)
tokenizer = Tokenizer(context)
}
fun generateResponse(input: String): String {
val tokens = tokenizer.encode(input)
val output = Array(1) { FloatArray(100) }
interpreter.run(tokens, output)
return tokenizer.decode(output[0])
}
}
Integrate the AI agent into the ViewModel:
class ChatViewModel(
private val chatClient: ChatClient = ChatClient.instance(),
private val aiAgent: AiAgent = AiAgent.get()
) : ViewModel() {
// ... existing code ...
fun sendMessage(text: String) {
viewModelScope.launch {
val channel = chatClient.channel("messaging", "general")
channel.sendMessage(Message(text = text)).await()
// Check if network is available
if (!isNetworkAvailable()) {
val aiResponse = aiAgent.generateResponse(text)
channel.sendMessage(Message(text = aiResponse)).await()
}
}
}
private fun isNetworkAvailable(): Boolean {
val connectivityManager = getApplication<Application>().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = connectivityManager.activeNetwork ?: return false
val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
}
7. Testing the Application
Run the app on an emulator or physical device. Test the following scenarios:
- Send a message while online - it should appear in real-time
- Turn on airplane mode and send a message - the AI agent should respond
- Turn off airplane mode - messages should sync with the server
8. Conclusion and Next Steps
You've built a real-time chat app with Stream's Android SDK, Jetpack Compose UI, and offline AI agents. To enhance this further, consider adding:
- Message persistence
- User authentication
- Rich media support (images, videos)
- Push notifications
- More advanced AI models
9. Frequently Asked Questions
Q: Can I use Stream's free tier for production? A: Stream's free tier is suitable for development and small projects. For production, consider their paid plans for better support and higher limits.
Q: How do I train my own AI model for the offline agent?
A: You can train a TensorFlow Lite model using TensorFlow and convert it to the .tflite format. Stream's documentation has guides on model optimization for mobile.
Q: Does the AI agent work on all Android versions?
A: TensorFlow Lite supports Android API level 21 and above. Make sure your minSdkVersion is set accordingly.
Ready to build more? Check out Tamiz's Insights for more advanced Android development tutorials and best practices.