This is Part 1 of a series of articles where I explain how to implement GenAI on Android. [Click here to view the full series.]

Making use of AI to summarise text
I thought on-device AI was still a distant dream — until I plugged ML Kit’s new GenAI summarisation API into my side-project, SmartWriter. Five minutes later, my phone was generating smart summaries from messy blocks of text — with zero cloud calls and no OpenAI keys.
In this post, I’ll show you exactly how I built it using Jetpack Compose and MVVM — and you can try it yourself because the full code is live here:
📲 What we’re building
The app has a Summarise screen:
Paste or type a long message, hit a button, and get a smart, clean summary — all processed locally on your phone.
🔍 Example:
Input:
Hey John, just a quick reminder that we’ve got the product demo with the client at 10:00 a.m. tomorrow. Make sure you’ve updated the pitch deck and double-checked the data from last week. Also, the UX team had a few notes about the prototype that might be worth bringing up.
Output:
Reminder: Client demo at 10 a.m. tomorrow. Update pitch deck and check UX notes.
🧱 Project setup
Add this dependency to your libs.versions.toml:
mlkit-genai-summarization = "com.google.mlkit:genai-summarization:1.0.0-beta1"
Then in your build.gradle.kts:
dependencies {
implementation(libs.mlkit.genai.summarization)
}
✅ You must use Android 14+
📱 Only works on compatible devices (e.g. Galaxy S25 Ultra, Pixel 8 Pro) that have hardware support for AI. Emulators don’t support Gemini Nano (yet).
🧩 The UI (Jetpack Compose)
Here’s a simplified version of my SummarisationScreen:
@Composable
fun SummarisationScreen(
uiState: SummarisationUiState,
onInputTextChanged: (String) -> Unit,
onSummariseClicked: (Context) -> Unit,
) {
val context = LocalContext.current
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
OutlinedTextField(
value = uiState.inputText,
onValueChange = onInputTextChanged,
label = { Text("Enter text to summarise") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { onSummariseClicked(context) },
enabled = uiState.inputText.isNotBlank() && !uiState.isLoading
) {
if (uiState.isLoading) CircularProgressIndicator(color = Color.White)
else Text("Summarise")
}
Spacer(modifier = Modifier.height(24.dp))
if (uiState.summary.isNotBlank()) {
Text("Summary:", style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.height(8.dp))
Text(uiState.summary)
}
}
}
🧠 ViewModel
This version includes a simplified version of the viewmodel logic. You can find the full implementation in the GitHub repo.
fun onSummariseClicked(context: Context) = viewModelScope.launch {
// I chose these options but feel free to inject these or use others
val options = SummarizerOptions.builder(context)
.setInputType(InputType.ARTICLE)
.setOutputType(OutputType.ONE_BULLET)
.setLanguage(Language.ENGLISH)
.build()
val summarizer = Summarization.getClient(options)
val status = summarizer.checkFeatureStatus().await()
when (status) {
FeatureStatus.AVAILABLE -> {
val request = SummarizationRequest.builder(uiState.value.inputText).build()
_uiState.update { it.copy(isLoading = true) }
val summary = summarizer.runInference(request).await().summary
_uiState.update { it.copy(summary = summary, isLoading = false) }
}
FeatureStatus.DOWNLOADABLE -> {
// visit the GitHub repository for the full implementation
summarizer.downloadFeature().await()
onSummariseClicked(context) // retry after download
}
else -> {
_uiEvent.emit(SummarisationUiEvent.Error("Your device does not support this feature."))
}
}
}
Job Offers
⚠️ Real-world limitations I hit
Here’s what tripped me up while building this:
📱 1. Device Compatibility
My original phone didn’t support Gemini Nano. Emulators didn’t work either.
So… I bought a Samsung Galaxy S25 Ultra — and everything worked instantly.
🌍 2. Language Support
At launch, the summarisation model supports only English, Korean, and Japanese.
This was a bit upsetting as a Spanish native speaker who wanted to build something in Spanish.
✉️ 3. Input Type Restrictions
I’m currently using InputType.ARTICLE as one of my summariser options. Some messages didn’t summarise and threw an error — likely because they weren’t “article-like.”
Regardless, my plan is to add support for InputType.CONVERSATION soon.
💬 Final thoughts
Even with the constraints above, the GenAI summariser is an amazing addition to Android’s toolkit. No cloud. No latency. No user data leaving the device.
It’s one of those features that — once it works — feels like magic. Just a clean result, instantly.
🚀 Try it yourself
Check out the full working app (including better error handling and download feedback):
This was just Part 1 — next up: Proofreading, powered by GenAI.
Follow me here on Medium so you don’t miss the next parts.
This article was previously published on proandroiddev.com


