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
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
- Go to Clipdrop and sign up for an account.
- Navigate to the API section in your dashboard.
- Generate a new API key. Keep this key private—it’s your access to the service.
Secure Your API Key
- APKs can be decompiled, revealing the key.
- If the key is leaked, others can misuse it, potentially incurring charges.
1. Open local.properties and add:
CLIPDROP_API_KEY=6f8b06caf423dd39d3d5707f488243199475046ee19db281095192f2f6d12. 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_KEYHow 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.
Step 2. Adding Dependencies
implementation("com.squareup.okhttp3:okhttp:5.1.0")Step 3:INTERNET PERMISSION
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"/>Step 4: Writing the API Call Function
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
// 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.
