Installation
Gradle (Kotlin DSL)
Copy
dependencies {
implementation("com.aallam.openai:openai-client:3.8.2")
implementation("io.ktor:ktor-client-okhttp:2.3.12") // or another Ktor engine
}
Gradle (Groovy)
Copy
dependencies {
implementation 'com.aallam.openai:openai-client:3.8.2'
implementation 'io.ktor:ktor-client-okhttp:2.3.12'
}
Configuration
Copy
import com.aallam.openai.api.http.Timeout
import com.aallam.openai.client.OpenAI
import com.aallam.openai.client.OpenAIConfig
import com.aallam.openai.client.OpenAIHost
import kotlin.time.Duration.Companion.seconds
val config = OpenAIConfig(
token = "lmnfl_your_api_key",
host = OpenAIHost(baseUrl = "https://api.lumenfall.ai/openai/v1"),
timeout = Timeout(socket = 60.seconds)
)
val openAI = OpenAI(config)
Security consideration for Android apps: API keys embedded in Android apps can be extracted by users through APK decompilation, reverse engineering, or debugging tools. Anyone with access to your API key can make requests at your expense.For production apps, consider:
- Proxy through your backend: Route API calls through your own server that holds the API key securely
- Per-user authentication: Issue individual API keys to authenticated users with appropriate rate limits
- Usage monitoring: Set up alerts for unusual usage patterns in your Lumenfall dashboard
- ProGuard/R8: While obfuscation helps, it does not fully protect embedded secrets
Using environment variables
Copy
val config = OpenAIConfig(
token = System.getenv("LUMENFALL_API_KEY"),
host = OpenAIHost(baseUrl = "https://api.lumenfall.ai/openai/v1")
)
val openAI = OpenAI(config)
Generate images
Copy
import com.aallam.openai.api.image.ImageCreation
import com.aallam.openai.api.image.ImageSize
import com.aallam.openai.api.model.ModelId
import com.aallam.openai.client.OpenAI
suspend fun generateImage(openAI: OpenAI): String {
val images = openAI.imageURL(
creation = ImageCreation(
prompt = "A serene mountain landscape at sunset with dramatic clouds",
model = ModelId("gemini-3-pro-image"),
n = 1,
size = ImageSize.is1024x1024
)
)
return images.first().url
}
Generation options
Copy
val images = openAI.imageURL(
creation = ImageCreation(
prompt = "A beautiful garden with roses",
model = ModelId("gpt-image-1.5"),
n = 1,
size = ImageSize.is1792x1024, // landscape
quality = Quality.HD,
style = Style.Natural
)
)
Available sizes
| Size | Dimensions |
|---|---|
ImageSize.is256x256 | 256x256 |
ImageSize.is512x512 | 512x512 |
ImageSize.is1024x1024 | 1024x1024 |
ImageSize.is1024x1792 | 1024x1792 (portrait) |
ImageSize.is1792x1024 | 1792x1024 (landscape) |
Get base64 response
Copy
import com.aallam.openai.api.image.ImageCreation
import com.aallam.openai.api.model.ModelId
val images = openAI.imageJSON(
creation = ImageCreation(
prompt = "A cute robot painting",
model = ModelId("gpt-image-1.5"),
n = 1
)
)
val base64Image = images.first().b64JSON
Edit images
Copy
import com.aallam.openai.api.image.ImageEdit
import com.aallam.openai.api.image.ImageSize
import com.aallam.openai.api.model.ModelId
import com.aallam.openai.api.file.FileSource
import okio.source
import java.io.File
suspend fun editImage(openAI: OpenAI): String {
val imageFile = File("original.png")
val images = openAI.imageURL(
edit = ImageEdit(
image = FileSource(
name = "original.png",
source = imageFile.source()
),
prompt = "Add a rainbow in the sky",
model = ModelId("gpt-image-1.5"),
n = 1,
size = ImageSize.is1024x1024
)
)
return images.first().url
}
With a mask
Copy
val imageFile = File("original.png")
val maskFile = File("mask.png")
val images = openAI.imageURL(
edit = ImageEdit(
image = FileSource(
name = "original.png",
source = imageFile.source()
),
mask = FileSource(
name = "mask.png",
source = maskFile.source()
),
prompt = "A sunlit indoor lounge area with a pool",
model = ModelId("gpt-image-1.5"),
n = 1,
size = ImageSize.is1024x1024
)
)
List models
Copy
suspend fun listModels(openAI: OpenAI) {
val models = openAI.models()
models.forEach { model ->
println(model.id.id)
}
}
Android example
Copy
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ImageGeneratorScreen()
}
}
}
@Composable
fun ImageGeneratorScreen(viewModel: ImageViewModel = viewModel()) {
var prompt by remember { mutableStateOf("") }
val imageUrl by viewModel.imageUrl.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
OutlinedTextField(
value = prompt,
onValueChange = { prompt = it },
label = { Text("Enter prompt") },
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = { viewModel.generateImage(prompt) },
enabled = !isLoading && prompt.isNotBlank()
) {
Text(if (isLoading) "Generating..." else "Generate")
}
imageUrl?.let { url ->
AsyncImage(
model = url,
contentDescription = "Generated image",
modifier = Modifier.fillMaxWidth()
)
}
}
}
ViewModel
Copy
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aallam.openai.api.image.ImageCreation
import com.aallam.openai.api.image.ImageSize
import com.aallam.openai.api.model.ModelId
import com.aallam.openai.client.OpenAI
import com.aallam.openai.client.OpenAIConfig
import com.aallam.openai.client.OpenAIHost
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class ImageViewModel : ViewModel() {
private val openAI = OpenAI(
OpenAIConfig(
token = BuildConfig.LUMENFALL_API_KEY,
host = OpenAIHost(baseUrl = "https://api.lumenfall.ai/openai/v1")
)
)
private val _imageUrl = MutableStateFlow<String?>(null)
val imageUrl = _imageUrl.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading = _isLoading.asStateFlow()
fun generateImage(prompt: String) {
viewModelScope.launch {
_isLoading.value = true
try {
val images = openAI.imageURL(
creation = ImageCreation(
prompt = prompt,
model = ModelId("gemini-3-pro-image"),
n = 1,
size = ImageSize.is1024x1024
)
)
_imageUrl.value = images.first().url
} catch (e: Exception) {
// Handle error
} finally {
_isLoading.value = false
}
}
}
}
Error handling
Copy
import com.aallam.openai.api.exception.OpenAIAPIException
import com.aallam.openai.api.exception.OpenAIHttpException
suspend fun generateImageSafely(openAI: OpenAI, prompt: String): String? {
return try {
val images = openAI.imageURL(
creation = ImageCreation(
prompt = prompt,
model = ModelId("gemini-3-pro-image"),
n = 1
)
)
images.first().url
} catch (e: OpenAIAPIException) {
when (e.statusCode) {
401 -> println("Invalid API key")
429 -> println("Rate limit exceeded")
402 -> println("Insufficient balance")
else -> println("API error: ${e.message}")
}
null
} catch (e: OpenAIHttpException) {
println("Network error: ${e.message}")
null
}
}
Concurrent image generation
Copy
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
suspend fun generateMultipleImages(
openAI: OpenAI,
prompts: List<String>
): List<String> = coroutineScope {
prompts.map { prompt ->
async {
val images = openAI.imageURL(
creation = ImageCreation(
prompt = prompt,
model = ModelId("gemini-3-pro-image"),
n = 1,
size = ImageSize.is1024x1024
)
)
images.first().url
}
}.awaitAll()
}