Firebase AI Logic: Generating and Streaming Gemini Responses in Flutter
Firebase AI Logic wraps the Gemini API for Flutter apps. Install the SDK, generate content, stream responses, and manage API keys safely.
Published on • October 15, 2026
AI Assistant

Adding a generative AI feature to a Flutter app used to mean picking between two uncomfortable options: call the Gemini API directly from the client and own the key-management problem yourself, or stand up a backend just to proxy a single request. Firebase AI Logic is Google’s answer for the client-side path — an SDK that wraps the Gemini API, handles authentication and abuse protection through Firebase, and ships as a normal Flutter plugin.
This guide covers the practical path: install, generate, stream, and keep the thing secure enough to ship.
What Firebase AI Logic actually is
Firebase AI Logic gives you client SDKs for Apple platforms, Android, Java, Web, Dart/Flutter, and Unity. It supports text generation, chat, multimodal analysis of images, video, audio and PDFs, structured JSON output, streaming, function calling, code execution, Google Search and Maps grounding, and a real-time bidirectional Live API.
On the Flutter side the package is firebase_ai, and the API surface is deliberately small: initialise Firebase, pick a backend, get a model, send a prompt.
Two backend options are offered:
- Gemini Developer API — recommended to start with, no cost to begin, uses a free tier. This is the default path.
- Agent Platform Gemini API (formerly Vertex AI) — the production-grade path, requires billing on your Google Cloud project.
The distinction matters mostly for quota and billing, not for your code shape.
Installing the SDK
The Flutter docs’ AI page points at Firebase AI Logic as the first option for building AI-powered Flutter apps, and the Firebase getting-started guide is the canonical setup. Requirements: Dart 3.2.0 or higher, and a Firebase project with the required APIs enabled.
From your Flutter project directory:
$ flutter pub add firebase_ai firebase_app_check
In lib/main.dart, import what you need and initialise Firebase before anything else:
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:firebase_app_check/firebase_app_check.dart';
import 'firebase_options.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const App());
}
DefaultFirebaseOptions comes from the FlutterFire configuration file generated by flutterfire configure. If you have not run that yet, do it first — the model calls will fail without a valid project behind them.
Generating content
Create a model instance, then send a prompt. The prompt is a list of Content objects, which is what makes multimodal input a natural extension rather than a separate API:
final model = FirebaseAI.googleAI().generativeModel(model: 'gemini-3.8-flash');
final response = await model.generateContent([
Content.text('Explain the BLoC pattern to a Flutter developer in three sentences.'),
]);
print(response.text);
FirebaseAI.googleAI() targets the Gemini Developer API. Swapping to the Agent Platform backend changes only that factory call — the rest of your code is unchanged.
response.text is nullable. A response can legitimately contain no text part — for example when a safety filter blocks the output — so treat null as a first-class outcome rather than an assert.
Streaming responses
Waiting for a complete response makes an AI chat feature feel sluggish. Firebase AI Logic streams text via generateContentStream (and sendMessageStream for multi-turn chat), delivering partial results as the model produces them. In a Flutter widget that maps cleanly onto a StreamBuilder or a plain await for:
final buffer = StringBuffer();
final stream = model.generateContentStream([
Content.text('Write a short product description for a mechanical keyboard.'),
]);
await for (final chunk in stream) {
final delta = chunk.text;
if (delta != null) {
buffer.write(delta);
// Rebuild your UI here with the growing buffer.
}
}
Each chunk is a partial GenerateContentResponse, so chunk.text is nullable for the same reasons response.text is. Guard every chunk, not just the final one.
Note the boundary the docs draw: generateContentStream is unidirectional text streaming. If you need low-latency, bidirectional, audio-capable conversation — a voice assistant, essentially — that is the separate Gemini Live API, which uses a LiveModel instance instead of a GenerativeModel.
Keeping API keys safe
This is the part teams get wrong. The historical failure mode is a hardcoded API key in a Flutter binary, extractable in an afternoon by anyone with a decompiler. Firebase AI Logic is designed so that you never ship a raw Gemini API key: your app authenticates as a Firebase app, and the protection layer is Firebase App Check.
Since early July 2026, the Firebase console’s setup workflow automatically enforces App Check for Firebase AI Logic. Starting November 2, 2026, App Check enforcement is required to use Firebase AI Logic at all. Plan for it now.
For local development you use the App Check debug provider, activated immediately after Firebase initialises:
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
await FirebaseAppCheck.instance.activate(
androidProvider: AndroidProvider.debug,
appleProvider: AppleProvider.debug,
);
Run the app, read the debug token from the logs (or the Xcode console on iOS), then register it under Security → App Check → Apps in the Firebase console via Manage debug tokens.
Three rules that matter more than the code:
- Never commit a debug token, and never ship a debug build. The debug provider exists to let you develop with enforcement on, not to bypass enforcement in production.
- Register a production attestation provider before release. App Attest on iOS, Play Integrity on Android, reCAPTCHA Enterprise on web. Without it, your real users fail App Check once enforcement is on.
- Restrict the underlying API key too. In the Google Cloud console, add application restrictions (Android app package + SHA-256, iOS bundle ID, or HTTP referrer) to the key associated with your Firebase project. App Check and key restrictions are complementary, not interchangeable.
Model choice and the version treadmill
Model names are hard-coded into your app at build time, which is a maintenance problem. The Firebase docs explicitly recommend using Firebase Remote Config or server-side prompt templates so you can change the model name on demand without shipping a new app version. This is not an optional optimisation — model families get deprecated, and a client that hardcodes a retired model name is a client with a broken feature.
The docs’ own example uses gemini-3.8-flash, and newer models such as Gemini 4 Argon are available through the same interface. Check the models page before you commit, and keep the name in Remote Config.
Practical pitfalls
Initialising the model before Firebase. Every factory call assumes Firebase.initializeApp has completed. In a Flutter app that means WidgetsFlutterBinding.ensureInitialized() followed by an awaited Firebase.initializeApp at the top of main.
Treating response.text as non-null. It is String?. Safety blocks, malformed responses, and unexpected part types all produce null. Surface a user-visible fallback rather than crashing.
Ignoring errors from the stream. Network drops mid-stream are normal on mobile. Wrap the await for loop in a try/catch and decide what the UI shows for a partial response.
Forgetting cancellation. If the widget that owns the stream is disposed while the model is still generating, cancel the subscription. Leaked subscriptions keep streaming into a dead setState.
Not handling quota and rate limits. The Gemini Developer API free tier is generous but finite, and it is per-project. Design your UI to degrade gracefully on a 429 rather than showing a spinner forever.
Confusing streaming with the Live API. generateContentStream gives you tokens faster. It does not give you a full-duplex audio session. If your feature is a voice conversation, you want the Live API.
Where to go next
Once text generation works end to end, the natural expansions are structured output (JSON mode for reliable parsing), multi-turn chat, and multimodal prompts — pass a photo or a PDF alongside the text. For agentic features with tool calls, the function-calling guide and the Flutter AI Toolkit are the next stops. Google also publishes a full showcase app covering these capabilities if you would rather read a working codebase than a prose guide.