# PRD to Flutter APK (`vigorous_spit/prd-to-flutter-apk`) Actor

Turn a JSON product spec into a compiling Flutter app: widget tree, Riverpod state, static analysis with an automatic repair loop, then a packaged Android APK. Generating and reading the source is free — you only pay for the packaged APK. Open source, MIT.

- **URL**: https://apify.com/vigorous\_spit/prd-to-flutter-apk.md
- **Developed by:** [Andres Gomez](https://apify.com/vigorous_spit) (community)
- **Categories:** Developer tools, Open source, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.01 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## PRD to Flutter APK

Give it a JSON description of an app. Get back Flutter source that compiles —
and, if you want it, an installable Android APK.

**Generating and reading the source is free.** You only pay for the packaged
APK. That way you can see exactly what it writes for *your* spec before deciding
whether it's worth anything to you.

### What it actually does

Four stages, each one handing off to the next:

1. **Plan** — turns your spec into a design and a `pubspec.yaml`
2. **UI** — writes the widget tree
3. **State** — wires Riverpod providers, models and Firestore access into it
4. **QA** — runs `flutter analyze` and the generated widget tests

When the analyser finds something, the diagnostic goes back to whichever stage
owns that file — a broken widget to the UI stage, a missing provider to the
state stage — and that stage gets another pass. Up to three rounds, then it
stops and hands you the diagnostics rather than pretending it worked.

That loop is the whole point. "The model wrote Dart" and "the Dart compiles"
are different claims, and only the second one is worth paying for.

### Input

```json
{
  "app_name": "Field Notes",
  "package_name": "com.example.fieldnotes",
  "theme": "material",
  "auth": true,
  "models": [
    {
      "name": "Observation",
      "collection": "observations",
      "fields": [
        { "name": "title", "label": "Title", "type": "text" },
        { "name": "count", "label": "Specimen count", "type": "number" }
      ]
    }
  ],
  "screens": [
    {
      "id": "observations",
      "title": "Observations",
      "kind": "list",
      "model": "Observation",
      "actions": [{ "name": "openCapture", "kind": "navigate", "target": "capture" }]
    },
    {
      "id": "capture",
      "title": "New observation",
      "kind": "form",
      "model": "Observation",
      "actions": [{ "name": "saveObservation", "kind": "create", "target": "Observation" }]
    }
  ]
}
```

`app_name`, `package_name` (reverse-DNS) and `screens` are required. Screens can
be `list`, `form`, `detail`, `auth` or `settings`. Actions can navigate, create,
update, delete, or sign in and out.

### Output

Every generated file lands in the dataset — one record per file, with its full
contents — so you can read the code directly in the console.

`OUTPUT` in the key-value store summarises the run:

```json
{
  "app_name": "Field Notes",
  "files_generated": 20,
  "diagnostics": [],
  "clean": true,
  "packaged": true,
  "apk": "https://api.apify.com/v2/key-value-stores/.../app.apk?signature=..."
}
```

`clean: true` means `flutter analyze` had nothing to say. If it isn't clean, the
diagnostics are listed — a build that failed tells you why rather than failing
silently.

With **Package an APK** enabled you also get `app.apk` in the key-value store,
built by Gradle. A recent run produced a 151 MB debug APK.

### The model

Leave **Anthropic API key** empty and it uses a built-in template generator: the
output compiles and the structure is real, but it isn't model-designed.

Supply your own key and the four stages are driven by Claude, which is what the
example above came from — it produced a shared widget for loading and error
states, a Firestore query scoped to the signed-in user, friendly messages mapped
from Firestore error codes, and a derived provider totalling a numeric field that
the input spec never asked for.

Your key is used for that run and not stored.

### What you don't get

- **No `firebase_options.dart`.** It holds project credentials, so it is never
  generated. The app reads its Firebase config from `--dart-define` values at
  build time and degrades rather than crashing when none are supplied.
- **No signed release build.** The APK is a debug artifact. Signing needs your
  keystore, which is yours.
- **Not a replacement for writing the app.** It is a scaffold that compiles and
  a starting point that isn't a blank page.

### Source

MIT licensed: [github.com/carlosge492/app-generation-microservice](https://github.com/carlosge492/app-generation-microservice)

A complete, unmodified example of the output — 23 generated files from a
45-line spec, plus a short note describing the run — is checked into
[`examples/generated-field-notes/`](https://github.com/carlosge492/app-generation-microservice/tree/master/examples/generated-field-notes)
if you'd rather read the code than take any of this on trust.

# Actor input Schema

## `prd` (type: `object`):

The app to build, as JSON. Requires app\_name, package\_name (reverse-DNS) and screens; models, theme and auth are optional. The full JSON Schema is served at /schema/prd.json by the hosted service.

## `anthropicApiKey` (type: `string`):

Your own key, used only for this run and never stored. Leave empty to use the template generator, which needs no key and produces a deterministic app rather than a designed one.

## `model` (type: `string`):

Ignored unless an Anthropic key is supplied.

## `packageApk` (type: `boolean`):

Off: generate and analyse the source only, which is free and takes about half the time. On: also run Gradle and produce an installable APK, which is the paid step.

## `maxRepairs` (type: `integer`):

How many times the loop may feed diagnostics back to the agent that owns them before giving up.

## Actor input object example

```json
{
  "prd": {
    "app_name": "Field Notes",
    "package_name": "com.example.fieldnotes",
    "description": "A small field-research capture app.",
    "theme": "material",
    "auth": true,
    "models": [
      {
        "name": "Observation",
        "collection": "observations",
        "fields": [
          {
            "name": "title",
            "label": "Title",
            "type": "text"
          },
          {
            "name": "count",
            "label": "Specimen count",
            "type": "number"
          },
          {
            "name": "verified",
            "label": "Verified",
            "type": "bool"
          }
        ]
      }
    ],
    "screens": [
      {
        "id": "observations",
        "title": "Observations",
        "kind": "list",
        "model": "Observation",
        "actions": [
          {
            "name": "openCapture",
            "kind": "navigate",
            "target": "capture"
          }
        ]
      },
      {
        "id": "capture",
        "title": "New observation",
        "kind": "form",
        "model": "Observation",
        "actions": [
          {
            "name": "saveObservation",
            "kind": "create",
            "target": "Observation"
          }
        ]
      }
    ]
  },
  "model": "claude-sonnet-5",
  "packageApk": false,
  "maxRepairs": 3
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "prd": {
        "app_name": "Field Notes",
        "package_name": "com.example.fieldnotes",
        "description": "A small field-research capture app.",
        "theme": "material",
        "auth": true,
        "models": [
            {
                "name": "Observation",
                "collection": "observations",
                "fields": [
                    {
                        "name": "title",
                        "label": "Title",
                        "type": "text"
                    },
                    {
                        "name": "count",
                        "label": "Specimen count",
                        "type": "number"
                    },
                    {
                        "name": "verified",
                        "label": "Verified",
                        "type": "bool"
                    }
                ]
            }
        ],
        "screens": [
            {
                "id": "observations",
                "title": "Observations",
                "kind": "list",
                "model": "Observation",
                "actions": [
                    {
                        "name": "openCapture",
                        "kind": "navigate",
                        "target": "capture"
                    }
                ]
            },
            {
                "id": "capture",
                "title": "New observation",
                "kind": "form",
                "model": "Observation",
                "actions": [
                    {
                        "name": "saveObservation",
                        "kind": "create",
                        "target": "Observation"
                    }
                ]
            }
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("vigorous_spit/prd-to-flutter-apk").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "prd": {
        "app_name": "Field Notes",
        "package_name": "com.example.fieldnotes",
        "description": "A small field-research capture app.",
        "theme": "material",
        "auth": True,
        "models": [{
                "name": "Observation",
                "collection": "observations",
                "fields": [
                    {
                        "name": "title",
                        "label": "Title",
                        "type": "text",
                    },
                    {
                        "name": "count",
                        "label": "Specimen count",
                        "type": "number",
                    },
                    {
                        "name": "verified",
                        "label": "Verified",
                        "type": "bool",
                    },
                ],
            }],
        "screens": [
            {
                "id": "observations",
                "title": "Observations",
                "kind": "list",
                "model": "Observation",
                "actions": [{
                        "name": "openCapture",
                        "kind": "navigate",
                        "target": "capture",
                    }],
            },
            {
                "id": "capture",
                "title": "New observation",
                "kind": "form",
                "model": "Observation",
                "actions": [{
                        "name": "saveObservation",
                        "kind": "create",
                        "target": "Observation",
                    }],
            },
        ],
    } }

# Run the Actor and wait for it to finish
run = client.actor("vigorous_spit/prd-to-flutter-apk").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "prd": {
    "app_name": "Field Notes",
    "package_name": "com.example.fieldnotes",
    "description": "A small field-research capture app.",
    "theme": "material",
    "auth": true,
    "models": [
      {
        "name": "Observation",
        "collection": "observations",
        "fields": [
          {
            "name": "title",
            "label": "Title",
            "type": "text"
          },
          {
            "name": "count",
            "label": "Specimen count",
            "type": "number"
          },
          {
            "name": "verified",
            "label": "Verified",
            "type": "bool"
          }
        ]
      }
    ],
    "screens": [
      {
        "id": "observations",
        "title": "Observations",
        "kind": "list",
        "model": "Observation",
        "actions": [
          {
            "name": "openCapture",
            "kind": "navigate",
            "target": "capture"
          }
        ]
      },
      {
        "id": "capture",
        "title": "New observation",
        "kind": "form",
        "model": "Observation",
        "actions": [
          {
            "name": "saveObservation",
            "kind": "create",
            "target": "Observation"
          }
        ]
      }
    ]
  }
}' |
apify call vigorous_spit/prd-to-flutter-apk --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vigorous_spit/prd-to-flutter-apk"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/6OsRIY7J9lyPtblIg/builds/zNgdsD6scPlnQq8Ip/openapi.json
