Adding On-Device AI to an Android App with Gemini Nano

AndroidGemini NanoOn-Device AIML Kit
Share on LinkedIn Share on X Share on Reddit Share on HN Share on Bluesky

On-device AI finally crossed the line from demo to shippable on Android. With Gemini Nano running through AICore, you can summarize a chat thread, rewrite a message in a different tone, proofread text, or describe an image — all without a network call, without a per-request bill, and without user data leaving the phone. For a class of features that used to require a cloud round-trip, that's a meaningful shift.

The catch, and the thing most tutorials gloss over, is that on-device AI on Android is a conditional capability. It exists on a subset of devices, the model is small and task-specific, and you must design for graceful absence. Here's how to add Gemini Nano features properly, what the APIs actually give you, and where the sharp edges are.

The pieces: AICore, Gemini Nano, and ML Kit GenAI

Three layers matter:

For lower-level, prompt-based access there's the experimental generative APIs, but the ML Kit GenAI feature APIs are where reliability lives today.

Always check availability first

The single most important habit: never assume the feature exists. Device support is narrow (Pixel 9 family, recent Galaxy S flagships, and expanding), and even on a supported device the model may still need to download. Check status and drive UI from it.

val summarizer = Summarization.getClient(
    SummarizerOptions.builder(context)
        .setInputType(InputType.ARTICLE)
        .setOutputType(OutputType.THREE_BULLETS)
        .setLanguage(Language.ENGLISH)
        .build()
)

when (summarizer.checkFeatureStatus().await()) {
    FeatureStatus.AVAILABLE -> runSummary(summarizer)
    FeatureStatus.DOWNLOADABLE -> {
        // trigger download, show progress, then run
        summarizer.downloadFeature(downloadCallback)
    }
    FeatureStatus.DOWNLOADING -> showDownloadingState()
    FeatureStatus.UNAVAILABLE -> useCloudFallbackOrHideFeature()
}

UNAVAILABLE is not an edge case — it's the majority of the installed base right now. Decide up front whether the feature degrades to a cloud call or simply hides, and make that decision per feature.

Running a summarization

Once available, the API is stream-friendly, which matters for perceived latency because even on-device generation isn't instant:

val request = SummarizationRequest.builder(longText).build()

summarizer.runInference(request) { result ->
    // streamed chunks; append to UI as they arrive
    appendToUi(result.summary)
}

On a Pixel 9 I've seen short summaries complete in a couple of seconds with the model already resident. The first run after a cold start is slower because AICore loads the model into memory; subsequent runs are quick. Stream the output so the user sees progress rather than a spinner.

What the model is good at (and not)

Gemini Nano is a small model. Calibrate expectations accordingly:

Good fit Poor fit
Summarize a thread or article Multi-step reasoning
Rewrite tone (formal ↔ casual) Long-context analysis (many pages)
Proofread / fix grammar Factual Q&A requiring world knowledge
Describe an image for accessibility Anything needing consistent output across all devices

The task-specific ML Kit APIs exist precisely because a small model does far better on a narrow, well-defined job than on an open prompt. Lean into that. If your feature needs genuine reasoning or long context, that's a cloud job — possibly with RAG — and trying to force it onto Nano will disappoint users on the few devices that even run it.

Why bother, given the device limits

Three reasons make on-device worth the conditional complexity:

Privacy. The text or image never leaves the phone. For messaging, health, journaling, or anything sensitive, that's a feature you can put in marketing copy — and it sidesteps a pile of mobile privacy and GDPR obligations that come with shipping user content to a server.

Offline. Summarization on the subway, proofreading on a plane. No connectivity dependency.

Cost. Zero marginal cost per inference. At scale, moving even a fraction of requests on-device meaningfully dents a cloud LLM bill.

Production checklist

Things I'd verify before shipping a Gemini Nano feature:

On-device AI is a great addition to the Android toolbox — it's not a replacement for cloud models, it's a complementary tier for well-scoped, privacy-sensitive, offline-friendly tasks. Treat it as one, design for absence, and it becomes a differentiator rather than a support headache. It pairs naturally with the broader on-device AI for Android privacy story.

Prompt injection on-device still matters

Even without network exfiltration, malicious text in a summarization input can manipulate output ("ignore previous instructions"). Sanitize inputs: max length caps, block known jailbreak prefixes, and display summaries as assistive not authoritative for medical or legal content.

AICore storage and enterprise MDM

Enterprise devices with MDM may block Google Play system updates — AICore never arrives, Nano stays UNAVAILABLE. Detect MDM-restricted devices and document support matrix for IT procurement. Offer cloud fallback behind explicit enterprise policy toggle.

Benchmarking on minimum hardware

Before marketing "on-device AI," test on your minimum supported device, not the Pixel on your desk:

fun benchmarkSummary(text: String, iterations: Int = 10): Stats {
    val times = (1..iterations).map {
        measureTimeMillis { summarizer.runInferenceBlocking(text) }
    }
    return Stats(p50 = times.percentile(50), p95 = times.percentile(95))
}

Publish p95 latency in feature specs — "2–8 seconds on supported devices" sets expectations better than demo videos.

Feature flags per device capability

Remote config: summarization_enabled requires FeatureStatus.AVAILABLE AND Build.VERSION.SDK_INT >= 34 AND min RAM check. Gradual rollout by device model percentage — Pixel-first betas before Samsung matrix expansion.

Content safety filters

Apply on-device blocklist for regulated content categories before inference — reduces harmful output without sending text to cloud moderation. Log blocked attempts locally for abuse investigation; don't upload raw text.

User education strings

In-app FAQ: "Processed on this device" with link to privacy policy section. Reduces support tickets asking if data uploaded to cloud — transparency beats vague "AI-powered" badge.

Enterprise MDM blocking AICore

Document workaround path when MDM blocks Google system updates — feature gracefully off with IT admin documentation link. Procurement checklist includes MDM compatibility sign-off.

Resources

Frequently asked questions

What is Gemini Nano and where does it run?

Gemini Nano is Google's smallest Gemini model, designed to run on-device through the AICore system service on supported Android phones. Inference happens locally, so data never leaves the device and features work offline.

Which Android devices support Gemini Nano?

Gemini Nano runs on a limited set of higher-end devices with the required NPU and AICore support — Pixel 9 series, recent Samsung Galaxy S flagships, and a growing list. Always check availability at runtime and provide a fallback for unsupported devices.

Should I use Gemini Nano or a cloud model?

Use Gemini Nano when privacy, offline support, and zero per-request cost matter and the task is well-scoped (summarize, rewrite, proofread, describe). Use a cloud model for complex reasoning, long context, or when consistent quality across all devices is required.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →