The built-in OS model
Rules
- The OS owns the weights, but the model is still installed — as an identity:
fileType: ModelFileType.builtInwith.fromBundled(...). The app downloads nothing. - Call
BuiltInAi.ensureReady()beforegetActiveModel(), from a user action: the first call downloads the model and can take minutes, and on web the browser refuses to start that download without a user gesture. Call it straight from the tap handler, with no slowawaitin front of it. It throwsTimeoutExceptionaftertimeout— 10 minutes by default. - On web a missing gesture is not distinguishable by type:
ensureReadyrewraps it asBuiltInAiUnavailableExceptionwithunavailableOther, and only.messagecarries the browser's "NotAllowedError: Requires a user gesture". Read the message before concluding the device cannot do it — otherwise the fallback below downloads gigabytes for nothing. - Catch
BuiltInAiUnavailableExceptionand fall back to a downloadable model. - Android apps need
minSdk 26, or the manifest merge fails. - There is no Windows or Linux support — and
BuiltInAi.availability()does not report that, it throws a Flutter PlatformException (from package:flutter/services.dart) there. Guard by platform before calling it, as the setup below does.
Setup with a fallback
flutter pub add flutter_gemma flutter_gemma_builtin_ai flutter_gemma_litertlm
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_gemma/flutter_gemma.dart';
import 'package:flutter_gemma_builtin_ai/flutter_gemma_builtin_ai.dart';
import 'package:flutter_gemma_litertlm/flutter_gemma_litertlm.dart';
await FlutterGemma.initialize(
inferenceEngines: [BuiltInAiEngine(), LiteRtLmEngine()],
);
final spec = kIsWeb || defaultTargetPlatform == TargetPlatform.android
? BuiltInAiModels.geminiNano
: defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.macOS
? BuiltInAiModels.appleFoundationModels
: null; // Windows and Linux have no built-in model
Future<InferenceModel> downloadGemma() async {
await FlutterGemma.installModel(
modelType: ModelType.gemma4,
fileType: ModelFileType.litertlm,
).fromNetwork(
// 2.6 GB — ask first. On web: gemma-4-E2B-it-web.litertlm
'https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm/resolve/main/gemma-4-E2B-it.litertlm',
).install();
return FlutterGemma.getActiveModel(maxTokens: 1024);
}
InferenceModel model;
if (spec == null) {
model = await downloadGemma();
} else {
try {
await FlutterGemma.installModel(
modelType: ModelType.general,
fileType: ModelFileType.builtIn,
).fromBundled(spec.name).install();
// onProgress reports real percentages on web only: ML Kit gives no byte
// total on Android, and Apple downloads nothing — ensureReady just waits.
await BuiltInAi.ensureReady(onProgress: (int percent) => print('$percent%'));
model = await FlutterGemma.getActiveModel(maxTokens: 4096);
} on BuiltInAiUnavailableException {
model = await downloadGemma();
} on TimeoutException {
model = await downloadGemma();
}
}
The latest install is the one getActiveModel loads, so the fallback replaces the built-in model. Sessions and chats then work as in the flutter-gemma-inference skill.
Checking before offering the feature
final availability = await BuiltInAi.availability();
final usable = availability == BuiltInAiAvailability.available ||
availability == BuiltInAiAvailability.downloadable ||
availability == BuiltInAiAvailability.downloading;
downloadable and downloading mean "not yet" — ensureReady finishes the job. The unavailable* states describe the device now, not forever:
| State | Meaning |
|---|---|
BuiltInAiAvailability.unavailableDeviceUnsupported |
the hardware cannot run it — and on web, that the browser exposes no Prompt API at all, including desktop Chrome with the flag off |
BuiltInAiAvailability.unavailableOsTooOld |
an OS update would enable it; on Apple the floor is OS 26 |
BuiltInAiAvailability.unavailableDisabled |
the user can turn it on in system settings (Apple Intelligence on Apple devices) |
BuiltInAiAvailability.unavailableOther |
anything else: a probe that timed out after 20 seconds, or Chrome's reasonless "unavailable", which in practice is its disk and VRAM floor — worth asking again later |
Platforms
| Platform | Model | Needs |
|---|---|---|
| Android | Gemini Nano (AICore) | Pixel 9+, Galaxy S25+; minSdk 26 |
| iOS, macOS | Apple Foundation Models | iOS 26+ / macOS 26+ on an iPhone 15 Pro or newer, or an Apple Silicon Mac, with Apple Intelligence on. The package itself builds from iOS 15 / macOS 10.15, so no deployment-target bump |
| Web | Gemini Nano (Chrome Prompt API) | desktop Chrome — not mobile browsers, Firefox or Safari |
| Web | Phi-4-mini (Edge Prompt API) | Microsoft Edge with the Prompt API flag on; Edge Dev 154–155 exposes the API but cannot run the model |
Images work on Android only, one per message, and only when asked for: getActiveModel(maxTokens: 4096, supportImage: true) and createChat(supportImage: true). With the default supportImage: false the image is dropped with no warning. On Apple every image fails on every OS version — a Flutter PlatformException with code "IMAGE_UNSUPPORTED_OS" — the package builds against the OS 26 SDK, which has no attachment API — so do not build an image path there. The web model is text-only.
Web
There is no script to add: the Prompt API is part of the browser. It has to be enabled.
- Production — register the origin for the Prompt API origin trial and add the token to
web/index.html:
<meta http-equiv="origin-trial" content="YOUR_TOKEN_HERE">
- Local development — enable
chrome://flags/#prompt-api-for-gemini-nanoand restart Chrome. - Microsoft Edge — enable
edge://flags→ "Prompt API for on-device language model" and restart. Both browsers useBuiltInAiModels.geminiNano: the spec names the API, and the browser picks the model.
Trade-offs
No choice of weights, no LoRA, and capabilities that vary by OS version. The right pick when zero download and zero disk matter more than choosing the model.