Build FlutterFlow

KINDAreplaces $39/mosaves $468/yrback to the verdict

0%0 of 21 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A Flutter application built from a schema you define once: generated data models, a real authentication flow, list and detail and edit screens per entity, an offline cache that survives a train tunnel, and signed builds distributed to testers on both platforms.

estimated effort multi-daythe files for this build are in the project pack

FrameworkFlutter with Dart, on the current stable channelBackendSupabase, self-hosted or managedGenerationDart code generation from a schema file, run as a build stepLocal cacheSQLite through DriftStateRiverpod

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why The framework. The doctor also tells you which platform toolchains are missing before you waste time.

    Get it Install from flutter.dev following the guide for your operating system, then run the doctor and resolve every warning for your targets. open ↗

    Verify flutter doctor reports no issues for your intended platforms

  2. have readyfree if you have one

    Why Emulators hide performance, permissions and offline behaviour, which are three of the five things this build is about.

    Get it Any recent Android device with developer options and USB debugging enabled.

    Verify flutter devices lists it and flutter run installs to it

  3. have readyfree if you own a Mac; otherwise this is the largest cost in the project

    Why There is no supported way to build or sign an iOS application without macOS. This decides your platform scope.

    Get it Any Mac able to run a current Xcode. If you do not have one, decide in Phase 1 that this is Android only.

    Verify A Flutter application runs on a physical iPhone

  4. 99 USD per year

    Why Required to install on devices you do not own and to use the testing distribution channel.

    Get it Enrol at developer.apple.com/programs. Review can take several days, so start early if iOS is in scope. open ↗

    Verify A provisioning profile installs a build on a tester's device

  5. 25 USD one time, plus identity verification

    Why Needed for the Android testing channel and any public listing.

    Get it Register at the Play Console. There is a one-time fee and an identity verification step that takes time.

    Verify An internal testing release reaches a tester

  6. free tier available for managed; self-hosting costs a server

    Why Provides the database, authentication and API. Without it Phase 3 has nothing to talk to.

    Get it Create a project at supabase.com, or run the Docker stack yourself. Take the project URL and the anon key.

    Verify A query from the application returns rows

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- The worked example is a field inspection application. Replace these with your own entities in Phase 2 and keep the shape.
- schema.yaml · your entities, fields, types and relations, the single input to code generation
- sites(id, name, address, lat, lng, created_at)
- inspections(id, site_id, inspector_id, state, started_at, submitted_at, notes)
- findings(id, inspection_id, severity, description, photo_path, recorded_at)
- sync_queue(id, entity, entity_id, op, payload_json, attempts, last_error) · the outbox that makes offline work

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
SUPABASE_URLrequiredhttps://yourproject.supabase.coYour project settings. Public by design.
SUPABASE_ANON_KEYrequiredeyJhbGciOi...Project API settings. Safe in the client only because row level security is doing the work.
ANDROID_KEYSTORE_PATHsecretoptional./keys/upload.jksProduct phase only. Never committed. Back it up in two separate places.
ANDROID_KEY_ALIASoptionaluploadThe alias used when the keystore was created.
ANDROID_KEY_PASSWORDsecretoptionalreplace-with-your-passwordKept in a password manager, never in the repository.
SYNC_MAX_ATTEMPTSrequired8How many times a queued write retries before being marked failed and shown to the user.

The build, in order

  1. Toolchain, and the distribution decision

    A Flutter application runs on a real device on each platform you intend to ship to.

    1. Install the SDK, then resolve every doctor warning for your targets. Ignoring them now means debugging them later with more code in the way.

      terminal
      flutter doctor -v
      flutter create fieldapp
    2. Emulators hide performance and permission behaviour. Use a real device from the first day.

      terminal
      flutter devices
      flutter run -d <device-id>
    3. This requires Xcode, a signing identity and a provisioning profile. It is the single most likely thing to block you, so do it before writing code.

    done when · tick each as it passes
    watch out
    • Building for iOS requires macOS and Xcode, and distributing to testers requires a paid Apple Developer account before you can install on a device you do not own. If you do not have a Mac, decide now whether this is an Android-only application, because discovering it in Phase 6 wastes the work.
  2. Schema and generated models

    One schema file produces your data models, and changing it regenerates them.

    1. Entities with fields, types, nullability and relations. This file is the thing you maintain; everything else follows from it.

      Files schema.yaml

    2. A build step producing serialisable model classes and Drift table definitions. Run it as part of the build so it cannot be forgotten.

      Files lib/generated/

      terminal
      dart run build_runner build --delete-conflicting-outputs
    3. A schema change must produce a migration. Without it, the local database and the models diverge and the failure appears as mysterious query errors.

      Files lib/db/migrations/

    done when · tick each as it passes
    watch out
    • Hand-editing generated files is the habit that destroys the value of generation, because the next run silently discards the edit. Keep generated files clearly marked, never edit them, and put anything custom in a separate file that extends rather than modifies.
  3. Authentication and the data source

    A real person can sign in, and the data they see is the data they are allowed to see.

    1. Sign in, sign out, and token refresh. Store the token in secure storage, not in shared preferences.

      Files lib/auth/

    2. One policy per table, on the server. This is the actual access control and everything else is presentation.

      Files supabase/policies.sql

    3. Use a request tool with a second user's token and confirm the refusal. Testing through your own client proves nothing.

    done when · tick each as it passes
    watch out
    • Filtering by user in the client is not access control; anyone can call the API directly with the same token. Put the rule in a row level security policy, then verify by querying the API with a second user's token and confirming the refusal.
  4. Generated screens

    Each entity has list, detail and edit screens, generated from the schema and then refined by hand.

    1. A one-off generator writing into the normal source tree, not into the generated directory. Different rules for different kinds of code.

      Files tool/gen_screens.dart

    2. Required, type and length come from the field definitions, so validation cannot drift from the database.

      Files lib/forms/

    3. Change a generated screen, regenerate models, and confirm the change survives.

    done when · tick each as it passes
    watch out
    • Screens are where design judgement lives, so continuously regenerating them destroys every refinement you make. Generate screens once as a scaffold you then own, and keep continuous regeneration for models and migrations where there is no judgement to lose.
  5. Working offline

    The application is usable with no signal, and nothing entered is lost when the network returns.

    1. The network fills the cache; the interface never waits on it. This inverts the usual structure and is the whole of offline support.

      Files lib/repository/

    2. Queue writes with attempts and last error. Exponential backoff, and a cap after which it is marked failed rather than retried forever.

      Files lib/sync/outbox.dart

    3. Per record: synced, pending or failed. Not a global spinner, which tells the user nothing about their record.

      Files lib/widgets/sync_badge.dart

    done when · tick each as it passes
    watch out
    • An optimistic write with no visible pending state makes the user believe the data is safe when it exists only on their phone. Show pending and failed states plainly, because the moment that matters is when someone closes the application believing their work was submitted.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more no-code apps.