Building Real-World Jetpack Compose Apps: A Practical Journey

Building Real-World Jetpack Compose Apps: A Practical Journey


Introduction

Welcome, fellow Android developers! I'm Atrajit, a mathematics researcher and a passionate Android developer. Over time, I've built a number of real-world apps using Jetpack Compose, and in this blog-style tutorial, I want to walk you through my journey. This isn't just a dry tutorial—it's a living narrative of practical experience, including useful design ideas, architectural patterns, and integrations with modern tools like GitHub, Gemini, and WhatsMeow.

Whether you're a beginner or an intermediate developer, you'll find real insight here to boost your Compose skills.


🏠 Project 1: JetChat — A Jetpack Compose Chat App with Notifications

✅ Features:

  • Beautiful Compose UI for messaging
  • Local notifications
  • Room database for storing messages
  • Media attachments support (image, audio, docs)

⚖️ Tech Stack:

  • Jetpack Compose
  • ViewModel + Hilt
  • Room + NotificationManager
  • Coroutine-based architecture

🔧 Code Snippets:

1. Message Data Model:

@Entity
data class Message(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val sender: String,
    val content: String,
    val timestamp: Long = System.currentTimeMillis(),
    val type: MessageType = MessageType.TEXT
)

enum class MessageType { TEXT, IMAGE, AUDIO, FILE }

2. Chat Screen UI:

@Composable
fun ChatScreen(viewModel: ChatViewModel) {
    val messages by viewModel.messages.collectAsState()

    LazyColumn(reverseLayout = true) {
        items(messages) { message ->
            MessageCard(message)
        }
    }

    MessageInput(onSend = { content ->
        viewModel.sendMessage(content)
    })
}

3. Sending Notification:

fun showNotification(context: Context, content: String) {
    val notification = NotificationCompat.Builder(context, "chat_channel")
        .setContentTitle("New Message")
        .setContentText(content)
        .setSmallIcon(R.drawable.ic_message)
        .build()

    NotificationManagerCompat.from(context).notify(1, notification)
}

🔍 Project 2: QuizHub — GitHub-Fetching Quiz App

✅ Features:

  • Navigate folders from GitHub repo
  • Parse and display quiz JSON
  • Quiz UI with result evaluation

⚖️ Tech Stack:

  • Jetpack Compose UI
  • Retrofit for GitHub API
  • Gson for JSON parsing

🔧 Code Snippets:

1. JSON Structure:

{
  "title": "Basic Algebra Quiz",
  "questions": [
    {
      "question": "What is 2 + 2?",
      "options": ["3", "4", "5", "6"],
      "answer": "4"
    }
  ]
}

2. Retrofit Setup:

interface GitHubService {
    @GET("repos/{owner}/{repo}/contents/{path}")
    suspend fun getFolderContent(
        @Path("owner") owner: String,
        @Path("repo") repo: String,
        @Path("path") path: String
    ): List
}

3. Quiz UI:

@Composable
fun QuizScreen(quiz: Quiz) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(text = quiz.title, style = MaterialTheme.typography.h5)
        quiz.questions.forEach { q ->
            Text(text = q.question)
            q.options.forEach { opt ->
                Button(onClick = { /* check answer */ }) {
                    Text(opt)
                }
            }
        }
    }
}

🧠 Project 3: AtrajitAI — Compose ChatGPT Clone with Gemini + Stability AI

✅ Features:

  • Chat UI like ChatGPT
  • Uses Gemini API for text
  • Uses Stability AI for image/audio
  • API key storage with encryption

⚖️ Tech Stack:

  • Jetpack Compose
  • EncryptedSharedPreferences
  • HTTP requests with OkHttp

🔧 Code Snippets:

1. Chat UI:

@Composable
fun AIAssistantChat(messages: List, onSend: (String) -> Unit) {
    LazyColumn {
        items(messages) {
            AIMessageCard(it)
        }
    }
    MessageInput(onSend)
}

2. Encrypted Preferences:

val masterKey = MasterKey.Builder(context).setKeyScheme(AES256_GCM).build()
val sharedPrefs = EncryptedSharedPreferences.create(
    context,
    "api_keys",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

3. Gemini Response Request:

suspend fun fetchGeminiResponse(prompt: String): String {
    val request = buildGeminiRequest(prompt)
    val response = client.newCall(request).execute()
    return response.body?.string() ?: ""
}

⚖️ Jetpack Compose Concepts Covered

  • Navigation: Dynamic routes using NavHost, composable("screen/{id}")
  • State: ViewModel + remember + mutableStateOf
  • Networking: Retrofit, OkHttp
  • Persistence: Room, EncryptedSharedPreferences
  • Notifications: Local with NotificationCompat
  • Secure Storage: Jetpack Security

🧩 Mentor Notes: What I Share with Juniors

  • Always build from UI → Logic → Storage → API
  • Break down the features into small @Composables
  • Don't fear errors: they are debugging hints
  • Use GitHub projects to store quizzes, images, and shared data

📗 Final Challenge for You

  • Add dark/light theme switcher
  • Build profile management with editable info
  • Add Gemini voice responses
  • Enable document upload to GitHub from the app

Let this blog serve as a practical guidebook for creating real, fun, and educational apps using Jetpack Compose.

Happy coding, and always be curious. 🚀


Written by Atrajit Sarkar | MSc Math | Android Developer | Educator

Comments