DEV Community

Tamiz Uddin
Tamiz Uddin

Posted on Originally published at tamiz.pro

Building a Real-Time Chat App with Stream's Android SDK, Jetpack Compose, and Offline AI Agents

Originally published on tamiz.pro.

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

  • 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"
}
Enter fullscreen mode Exit fullscreen mode

Add the internet permission to your AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

Don't forget to register the Application class in the manifest:

<application
    android:name=".ChatApplication"
    ... >
    ...
</application>
Enter fullscreen mode Exit fullscreen mode

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))
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
            )
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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])
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)