Best Tutorial for Android Jetpack Compose

Android App Development

Stay ahead with the latest tools, trends, and best practices in Android development

AI Image Generator in Jetpack

AI Image Generator in Jetpack - Coding Bihar
Building a Professional AI Image Generator in Jetpack Compose

Building a Professional AI Image Generator in Jetpack Compose (2025)

Artificial intelligence has revolutionized content creation, and generating images from text prompts is now a reality for mobile apps. With Jetpack Compose, Android developers can build modern, professional UIs for AI-powered image generators, making the experience intuitive and visually appealing. In this blog post, we will walk through how to build a fully functional AI image generator app using the Clipdrop Text-to-Image API, and more importantly, how to secure your API key from reverse engineering.

Why Jetpack Compose is Ideal for AI Apps

Jetpack Compose offers a declarative UI approach, which allows dynamic updates without complex XML layouts. For an AI image generator:

Users can see the image loading in real-time.
Buttons, progress indicators, and prompts update seamlessly with state changes.
Animations, shadows, and gradients make the app feel polished.
Compose’s simplicity ensures that even complex workflows, like calling an API asynchronously and updating the UI, remain readable and maintainable.

Generating Images with Clipdrop API

The Clipdrop Text-to-Image API transforms text prompts into high-quality images. The workflow is straightforward:
  • User enters a prompt describing the desired image.
  • The app sends the prompt to the Clipdrop API using a secure API key.
  • The API responds with an image, which the app displays in a rounded card.
  • Users can optionally download the generated image to a designated folder.
fun generateImageFromClipdrop(prompt: String, apiKey: String): Bitmap? {
    val client = OkHttpClient()

    val requestBody = MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("prompt", prompt)
        .build()

    val request = Request.Builder()
        .header("x-api-key", apiKey)
        .url("https://clipdrop-api.co/text-to-image/v1")
        .post(requestBody)
        .build()

    return try {
        client.newCall(request).execute().use { response ->
            if (response.isSuccessful) {
                val contentType = response.header("Content-Type") ?: ""
                if (contentType.contains("image")) {
                    val bytes = response.body?.bytes()
                    return bytes?.let { BitmapFactory.decodeByteArray(it, 0, it.size) }
                } else {
                    val err = response.body?.string()
                    Log.e("Clipdrop", "Error JSON: $err")
                    null
                }
            } else {
                val err = response.body?.string()
                Log.e("Clipdrop", "Request failed: ${response.code}, $err")
                null
            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }
}

Downloading Images Safely

Users often want to save generated images. You can download generated images in Download folder, ensuring all downloads are organized:
fun saveImageToDownloads(context: Context, bitmap: Bitmap) {
    try {
        val filename = "clipdrop_${System.currentTimeMillis()}.png"
        val downloadsDir =
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
        val file = File(downloadsDir, filename)

        FileOutputStream(file).use { out ->
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
        }

        Toast.makeText(context, "Saved to Downloads/$filename", Toast.LENGTH_LONG).show()
    } catch (e: Exception) {
        e.printStackTrace()
        Toast.makeText(context, "Save failed: ${e.message}", Toast.LENGTH_SHORT).show()
    }
}

How to Register and Get our API Key

We’ll use Clipdrop’s Text-to-Image API for this app.
  1. Go to Clipdrop and sign up for an account.
  2. Navigate to the API section in your dashboard.
  3. Generate a new API key. Keep this key private—it’s your access to the service.
Important: Do not paste this key directly in your source code. Anyone with the key could make unlimited requests and potentially incur charges on your account.

Secure Your API Key

A critical part of building an AI image generator is ensuring your API key is not exposed. Hardcoding your API key in the source code is dangerous because:
  • APKs can be decompiled, revealing the key.
  • If the key is leaked, others can misuse it, potentially incurring charges.
Instead of hardcoding, use local.properties + BuildConfig:

1. Open local.properties and add:

CLIPDROP_API_KEY=your_api_key_here
CLIPDROP_API_KEY=6f8b06caf423dd39d3d5707f488243199475046ee19db281095192f2f6d1

2. In app/build.gradle, add:

android {
    defaultConfig {
        buildConfigField "String", "CLIPDROP_API_KEY", "\"${project.findProperty("CLIPDROP_API_KEY") ?: ""}\""
    }
}

3. Access in Kotlin:

val apiKey = BuildConfig.CLIPDROP_API_KEY
This keeps the key out of your source code and version control. For maximum security, you could also use a backend server that stores the key and forwards requests.

How to build a Professional Image Generator App in Jetpack Compose (2025)

Step 1. Set Up Your Project

  • Open Android Studio → New Project → Empty Compose Activity.
  • Minimum SDK: 29+ (required for storage and network operations).
  • Name your project: ImageGenerator.
This setup gives you a modern foundation with Compose, allowing for clean layouts and smooth UI updates.

Step 2. Adding Dependencies

In app/build.gradle, include:
implementation("com.squareup.okhttp3:okhttp:5.1.0")

Step 3:INTERNET PERMISSION

Ensure network permission is added in AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28"/>
Any app that makes network requests needs the Internet permission. Since your image generator app calls the Clipdrop API over HTTPs, you must declare it in your AndroidManifest.xml.
For Android 9 and below: If you want to save images to Downloads or external storage.
For Android 10 and above: You can use the MediaStore API to save images without requesting storage permission. This is the modern, safer approach.

Step 4: Writing the API Call Function

Here’s a function to send the prompt and get an image:
fun generateImageFromClipdrop(prompt: String, apiKey: String): Bitmap? {
    val client = OkHttpClient()

    val requestBody = MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("prompt", prompt)
        .build()

    val request = Request.Builder()
        .header("x-api-key", apiKey)
        .url("https://clipdrop-api.co/text-to-image/v1")
        .post(requestBody)
        .build()

    return try {
        client.newCall(request).execute().use { response ->
            if (response.isSuccessful) {
                val contentType = response.header("Content-Type") ?: ""
                if (contentType.contains("image")) {
                    val bytes = response.body.bytes()
                    return bytes.let { BitmapFactory.decodeByteArray(it, 0, it.size) }
                } else {
                    val err = response.body.string()
                    Log.e("Clipdrop", "Error JSON: $err")
                    null
                }
            } else {
                val err = response.body.string()
                Log.e("Clipdrop", "Request failed: ${response.code}, $err")
                null
            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }
}

Step 5: Downloading or Saving Images to a Custom Folder

You can save images to a Download folder in your device:
// Save to Downloads
fun saveImageToDownloads(context: Context, bitmap: Bitmap) {
    try {
        val filename = "clipdrop_${System.currentTimeMillis()}.png"
        val downloadsDir =
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
        val file = File(downloadsDir, filename)

        FileOutputStream(file).use { out ->
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
        }

        Toast.makeText(context, "Saved to Downloads/$filename", Toast.LENGTH_LONG).show()
    } catch (e: Exception) {
        e.printStackTrace()
        Toast.makeText(context, "Save failed: ${e.message}", Toast.LENGTH_SHORT).show()
    }
}

Step 6: Designing a Professional UI with Jetpack Compose

  • Gradient background for a modern feel.
  • Rounded buttons and input fields.
  • Shadowed card for generated image.
  • Loading indicator while fetching image.
  • Organized spacing and padding.

@Composable
fun ClipdropScreen() {
    val context = LocalContext.current
    var prompt by remember { mutableStateOf(TextFieldValue("")) }
    var imageBitmap by remember { mutableStateOf(null) }
    var isLoading by remember { mutableStateOf(false) }
    val coroutineScope = rememberCoroutineScope()
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(
                brush = Brush.verticalGradient(
                    colors = listOf(Color(0xFF6200EE), Color(0xFF03DAC5)) // Purple → Teal
                )
            )
            .padding(16.dp)
    ) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .systemBarsPadding()
                .padding(16.dp),
            verticalArrangement = Arrangement.Top,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text("Coding Bihar Image Generator",
                color = Color.Yellow,
                style = MaterialTheme.typography.headlineMedium)
            OutlinedTextField(
                value = prompt,
                onValueChange = { prompt = it },
                label = { Text("Enter your image prompt", color = Color.White) },
                modifier = Modifier.fillMaxWidth()
            )

            Spacer(Modifier.height(16.dp))

            Button(onClick = {
                if (prompt.text.isNotBlank()) {
                    val apiKey =
                        "6f8b06caf423dd39d3d5707f4889475046ee19db281095192f2f6d1816d37a93c5fb45075243ca667cec54855ce"
                    coroutineScope.launch(Dispatchers.IO) {
                        isLoading = true
                        val bmp = generateImageFromClipdrop(prompt.text, apiKey)
                        withContext(Dispatchers.Main) {
                            imageBitmap = bmp
                            isLoading = false
                        }
                    }
                } else {
                    Toast.makeText(context, "Enter a prompt", Toast.LENGTH_SHORT).show()
                }
            }) {
                Text("Generate Image")
            }

            Spacer(Modifier.height(16.dp))

            if (isLoading) {
                CircularProgressIndicator()
            }

            imageBitmap?.let { bmp ->
                Image(
                    bitmap = bmp.asImageBitmap(),
                    contentDescription = null,
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(300.dp)
                )

                Spacer(Modifier.height(12.dp))

                Button(onClick = { saveImageToDownloads(context, bmp) }) {
                    Text("Download Image")
                }
            }
        }
    }
}
Note: In this example, we are hardcoding the API key, which means placing the key directly in the source code as a literal value instead of storing it securely. This is only for demonstration purposes—you should use your own key and avoid hardcoding it in a real app.
How to build 👇 
with Jetpack Compose 

Output:

Special Message

Welcome to Coding