Skip to content
Development
Skill

/generate-images-with-firebase-ai

Use when generating or editing images from Flutter/Dart with Firebase AI Logic and a Gemini image model (Nano Banana), making the first call work, choosing Gemini Developer API vs Vertex AI, hitting quota, billing or App Check failures, getting empty or image-only responses,

From plugin
flutter-ai-skills
67036 skills

Claude Code

$ npx -y skills add evanca/flutter-ai-rules --skill generate-images-with-firebase-ai --agent claude-code

SKILL.md

generate-images-with-firebase-ai.SKILL.md
name: generate-images-with-firebase-ai
description: "Use when generating or editing images from Flutter/Dart with Firebase AI Logic and a Gemini image model (Nano Banana), making the first call work, choosing Gemini Developer API vs Vertex AI, hitting quota, billing or App Check failures, getting empty or image-only responses, sending a user photo as input, controlling aspect ratio or size, writing the image prompt, or deciding what to test."
license: MIT

Generating images with Firebase AI Logic

Gemini image models return interleaved text and image parts from one call. The response is a sequence to walk, not a string to read.

A request that comes back empty is usually a configuration problem rather than a bug in your code, so section 1 covers the three settings that cause it.

1. Three things that block the very first call

A first call that returns an error, an empty response, or a 403 is almost always one of these rather than your Dart. Rule them out before you debug code.

**Billing.** Image generation has no free tier. On a Spark-plan project the image models return `limit: 0` for `generate_content_free_tier_requests`, so the first request fails on quota having made zero requests. Text models do work on Spark, which means "my other Gemini call works" proves nothing. Upgrade to Blaze, then verify the current limits rather than trusting this note:

gcloud services quota list --service=generativelanguage.googleapis.com --consumer=projects/YOUR_PROJECT_ID

**App Check.** Firebase AI Logic enforces App Check when the project has it turned on. Otherwise the endpoint is open to anyone who extracts your config from the shipped client, and that config is public by design. Anything reachable from a device you do not control needs App Check. Debug builds attest with a debug provider, release builds with a real one. Web has a specific trap that costs an afternoon, described in `references/setup.md`.

**`responseModalities`.** Without it the model has no permission to return an image, so you get text describing the picture it would have drawn. Set both modalities, as in the call below.

2. The minimal call that works

final model = FirebaseAI.googleAI().generativeModel(
  model: 'gemini-3.1-flash-image',
  generationConfig: GenerationConfig(
    responseModalities: [
      ResponseModalities.text,
      ResponseModalities.image,
    ],
    imageConfig: ImageConfig(
      aspectRatio: ImageAspectRatio.landscape16x9,
      imageSize: ImageSize.size2K,
    ),
  ),
);

final response = await model.generateContent([
  Content.multi([
    TextPart(prompt),
    InlineDataPart('image/jpeg', selfieBytes), // omit for text-to-image
  ]),
]);

`FirebaseAI.googleAI()` is the Gemini Developer API. Prefer it: it needs no GCP surface of its own, and its free tier covers text. `FirebaseAI.vertexAI()` requires Blaze regardless of model and buys you GCP-side controls, so reach for it when the project already lives in Vertex rather than by default.

Do not pass `appCheck:` or `auth:` to `googleAI()`. Both parameters are deprecated in current `firebase_ai`; the instance resolves them from the `FirebaseApp` on its own.

For model IDs, aspect-ratio and size enums, and when Imagen beats Gemini, read `references/models.md`.

3. Reading the response

The convenience `.text` accessor does not capture the full sequence, so walk the parts directly. Pattern-match on the part type, which keeps the switch correct when the SDK adds new part types:

Uint8List? image;
final buffer = StringBuffer();
for (final part in candidate.content.parts) {
  switch (part) {
    case InlineDataPart(:final bytes):
      image ??= bytes;          // first image wins
    case TextPart(:final text):
      buffer.write(text);
    default:
      break;
  }
}

Keep the interpretation of a response in its own pure function taking a `Candidate`. `Candidate`, `Content`, `TextPart` and `InlineDataPart` are all publicly constructible, so that function is testable with real SDK types, no Firebase and no test doubles. It is the one seam in this stack that unit tests genuinely reach.

When no image comes back

An empty result surfaces as a blank error and reads like a client bug, which sends people debugging the wrong half of the system. The response carries the reason, so report it. In order:

| Check | Where | Means | | --- | --- | --- | | `response.promptFeedback?.blockReason` | before candidates | Your input was rejected. Read `blockReasonMessage` too. | | `response.candidates` empty | n/a | Nothing generated at all. | | `candidate.finishReason` | on the candidate | The model stopped: safety, recitation, or a token limit. `finishMessage` adds detail. | | No image but text present | after walking parts | It answered in prose instead of drawing. Usually a prompt problem. | | Everything empty, no reason | n/a | Say so plainly and let the user retry. This happens intermittently. |

Image-only responses, with no text at all, also happen intermittently on prompts that reliably return both. If you ask for text alongside the image, treat its absence as normal and degrade instead of throwing.

4. Sending a user photo

Downscale before you send. `InlineDataPart.toJson()` base64-encodes the bytes synchronously on the main isolate, so a full-size phone photo freezes the UI for seconds while the request is built. Gemini downsamples large images anyway, so you pay for detail that is then discarded. Scale at pick time rather than after:

final file = await picker.pickImage(
  source: ImageSource.gallery,
  maxWidth: 1280,
  maxHeight: 1280,
  imageQuality: 85,
);

Size the cap to your subject rather than to a habit. 1280px on the long edge holds a face or a single figure comfortably, while fine texture, legible text in the source, or a wide scene the model has to read across will want more. Match `mimeType` to what the picker actually returned.

5. Sizing the result

Use `ImageConfig` when

Read more
@evanca
Ships withflutter-ai-skills

36 Flutter and Dart skills your coding agent loads by itself, sourced only from official documentation. A skill is a folder with a SKILL.md file.

Stars on GitHub

Momentum

stars in total
670
Star history of evanca/flutter-ai-rules on GitHub, a chart by star-history.com

Other skills on flutter-ai-skills.

accessibility

accessibility

Use when working on accessibility, a11y, WCAG, ARIA, screen readers, keyboard nav, focus order, contrast, alt text, captions,…

bloc

bloc

Use when creating a Cubit or Bloc, modeling state with sealed classes or status enums, wiring…

dart-3-updates

dart-3-updates

Use when writing switch statements, refactoring if-else chains, creating data classes, choosing records vs classes, destructuring…

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills2w309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react5d252k
@facebook@facebookInstall Skill

Questions about generate-images-with-firebase-ai.

Which coding agents can use generate-images-with-firebase-ai?
generate-images-with-firebase-ai installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.