Blog Infos
Author
Published
Topics
, , , ,
Published

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:

👉 GitHub: SmartWriter

📲 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 UltraPixel 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

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

⚠️ 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 EnglishKorean, 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):

👉 GitHub Repo: SmartWriter

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

Menu