How I actually use AI coding agents day to day as a Flutter dev: plan first prompts, migrations, agent code review, guardrails, CLAUDE.md, and honest productivity gains.
Eighteen months ago, "AI in my editor" meant autocomplete that finished the line I was already typing. It was a party trick. Useful, occasionally spooky, but it never changed the shape of my day. Today I hand an agent a task, walk over to review someone's PR in another tab, and come back to a diff worth actually reading. That shift — from a suggestion engine that guesses your next token to an agent that plans, edits across a dozen files, runs the test suite, reads the failures, and reports back — is the single biggest change to how I work since I dropped setState for a proper state management setup years ago.
But agentic coding is not magic, and treating it like magic is exactly how you ship a subtle bug to production with your name on the commit. I have done that. I want to be honest about it. This post is the whole picture: how I actually use AI coding agents day to day at Shpper — across a Flutter app and a Firebase-backed stack — where they genuinely earn their keep, where they quietly bite, and the process I have built around them so the quality bar stays high instead of sliding.
Before the workflow, a quick definition, because the term gets thrown around loosely. Agentic coding is the difference between a tool that responds and a tool that acts. Old-school AI autocomplete is a single completion: you type, it predicts, you accept or reject one suggestion. An AI coding agent runs a loop — it reads your codebase, forms a plan, edits multiple files, executes commands (tests, the linter, the build), reads the output, and iterates until the task is done or it gets stuck. Tools like Claude Code, Cursor's agent mode, and Copilot's agent workflows all sit in this second category.
That loop is the whole story. The value is not that the model is smarter than autocomplete; it is that the agent can close the feedback cycle by itself. It runs flutter test, sees the red, and fixes it without me in the middle. My role moves up a level: from typing code to specifying intent and verifying output. Understanding that framing changes how you use these tools, so let me start with the mental model that took me far too long to internalize.
For the first month I used agents like a faster junior who could type. I would give a task, get code, skim it, ship it. That is the wrong model and it will burn you.
The model that actually works: an agent is an extremely fast, tireless, occasionally overconfident collaborator with no memory of your codebase and no stake in the outcome. It will never push back on a bad idea unless you build pushback into the loop. It does not get bored, which is a superpower on tedious work and a liability on work that needs someone to stop and ask "wait, should we even be doing this?"
Once I internalized that — the intelligence is real but the judgment is mine to supply — everything downstream got better. I stopped being surprised when a plausible-looking diff had a hole in it, because I stopped expecting the agent to own correctness. That is still my job. The agent owns throughput.
My single highest-leverage habit: I make the agent write a plan before it writes a line of code. Not "build me a checkout flow" — that is how you get 400 lines you then have to unwind, line by line, wondering which of them you can trust. Instead I ask for a plan I can veto in two minutes.
A prompt I reuse constantly looks like this:
Before writing any code, produce:1. The files you'll touch and why2. New types/functions with signatures only (no bodies)3. Edge cases you're accounting for4. What you're intentionally NOT doing, and why5. Anything you're unsure about or assumingWait for my approval before editing anything.
That last item — "anything you're unsure about or assuming" — earns its place every single time. It is where the agent surfaces the domain rule it guessed wrong, the abstraction it was about to reach for, the quiet decision to refactor half a module I never asked it to touch.
Reviewing a plan takes two minutes and catches the expensive mistakes early: the misunderstood requirement, the wrong data flow, the scope creep. It is cheap to correct a plan and expensive to correct a diff. A plan is five bullet points; a diff is 300 lines of context you have to reload into your head. This one step probably saves me more time than everything else combined, and it costs almost nothing.
The subtle win is what it does to my thinking. Forcing the agent to enumerate edge cases means I read a clean list of them before any code exists — and half the time I catch a case I had not considered. The plan is not just a leash on the agent; it is a design review I would have skipped on my own. If your agent supports a dedicated planning or "ask" mode that reads but does not edit, use it for exactly this. Separating the decide phase from the do phase is the closest thing to a cheat code I have found.
This is where agents are almost unreasonably good, and it is the use case I would defend hardest. Renaming a concept across 60 files. Converting a batch of widgets to a new constructor pattern. Bumping a dependency with breaking API changes and fixing every callsite. Migrating a repository signature and threading the change through everything that calls it. This is tedious, error-prone work for a human — the kind where your attention drifts on file 40 and you miss one — and near-perfect work for an agent, because the change is well-defined and the test suite tells you the instant it is wrong.
When I changed a core model's field from a loose String to a typed enum recently, the agent walked every usage, updated the Firestore serialization on both the read and write side, fixed the tests, and left the tricky serialization cases flagged in its summary. My job shrank to reviewing exactly the part that needed judgment — how we handle a wire value that no longer maps to a known enum case — instead of the 55 mechanical edits around it.
// Old: a stringly-typed field, validated nowhere, spelled three different waysfinal status = data['status'] as String;// New: parse at the boundary, fail loud in dev, degrade safe in prodenum OrderStatus { pending, paid, shipped, cancelled; static OrderStatus fromWire(String? raw) { return OrderStatus.values.firstWhere( (s) => s.name == raw, orElse: () { assert(false, 'Unknown OrderStatus from wire: $raw'); return OrderStatus.pending; // safe default in release }, ); } String toWire() => name;}The agent produced the boilerplate — the enum, the parse helper, all the callsite edits — in one pass. I supplied the one line of actual policy: what happens on an unknown value. That division of labor is the whole game. It writes the 90% that is mechanical; I own the 10% that is a decision.
A tip that compounds here: give the agent a way to verify itself. On a migration, the test suite is the spec. If the change is well covered by tests, the agent can iterate to green on its own and hand me something that already passes. If it is not covered, I write or specify the tests first (more on that below), then let it loose. The presence or absence of a fast, honest feedback loop is the single biggest predictor of whether an agent-driven change lands clean.
Underrated use, and the one I would tell a skeptical senior to try first: I run an agent as a first-pass reviewer on my own pull requests before a human sees them. It is genuinely good at the boring-but-real stuff — an unawaited Future, a missing dispose() on a controller, a null path I did not handle, an N+1 query hiding in a loop, a setState after an await with no mounted check.
It catches these precisely because I wrote the code and am blind to it. I know what I meant, so I read what I meant. The agent has no such loyalty to my intentions; it reads what is actually there. A prompt as blunt as this pulls its weight:
Review this diff like a hostile senior engineer. Only flag real issues:race conditions, resource leaks, unhandled errors, missing null checks,anything that breaks under a bad network or an empty list. Skip style.If you find nothing serious, say so — don't invent problems.
The "don't invent problems" line matters. Without it, an agent will manufacture nitpicks to look useful, and you learn to ignore its output, which defeats the point. Give it permission to say "this looks fine" and its signal-to-noise gets dramatically better. This is also a great place to point the agent at your own conventions: "flag any Firestore query that renders a list but is not paginated" turns a generic reviewer into one that enforces your rules.
There is a whole category between "write the feature" and "review the feature" where agents quietly save an hour a day. Writing the one-off script to backfill a Firestore collection. Generating a fixture from a real document. Turning a Slack thread of requirements into a checklist. Explaining what a gnarly regex or a stranger's StreamBuilder actually does before I touch it. Drafting the boring half of a commit message. None of this is impressive. All of it used to be friction, and friction is where days leak away.
None of this is free. The failure modes are real, specific, and you learn them the hard way — usually once each. Knowing them by name is half the defense.
The process matters more than the model. A strong model with a sloppy loop ships worse code than a weaker model with a tight one. Here is the workflow I actually enforce — the guardrails that let me move fast without lowering the bar.
1. Small, reviewable diffs. I scope tasks so the output fits in my head. If a change starts sprawling across too many concerns, I stop and re-scope rather than push through. A diff I cannot fully review is a diff I do not trust, and trust is the only thing that makes any of this faster.
2. Tests are the contract, and I hold the pen. For anything non-trivial I write, or at least fully specify, the test cases myself — then let the agent make them pass. Reversing that order — agent writes the code and its own tests — is precisely how bugs get blessed into the suite. I decide what "correct" means; the agent's job is to satisfy it.
3. Everything runs in CI, agent or not. The agent's code clears the exact same gate mine does. No exceptions, no "it's just a small change." The gate does not care who typed it.
# The bar every change clears — human or agent, no exceptionssteps: - run: dart analyze --fatal-infos --fatal-warnings - run: dart format --set-exit-if-changed . - run: flutter test --coverage - run: dart run build_runner build --delete-conflicting-outputs
4. A living project instructions file (CLAUDE.md). I keep a CLAUDE.md — the equivalent works as .cursorrules or a Copilot instructions file — that encodes the house rules: state management conventions, the Firebase-must-cost-nothing constraint, error-handling patterns, the "never do X in this module" list, which packages we standardize on. It is the difference between an agent that fits the codebase and one that fights it on every task. Treat it like documentation you actually maintain — because a stale instructions file is worse than none; it confidently points the agent at conventions you abandoned six months ago.
Here is the flavor of what lives in mine:
## Non-negotiables- Firebase stays on the free tier. No Cloud Functions. Binaries ship via GitHub Releases, never Cloud Storage egress.- Never block the main isolate. Heavy parse/crypto goes to `compute()`.- All Firestore reads that render lists must be paginated. No unbounded queries.- State: Riverpod. Do not introduce a second state solution.- Errors surface through `AppFailure`, never raw exceptions to the UI.
Five minutes to write, and it kills a whole class of "the agent did a reasonable thing that is wrong here" problems before they start. The rule of thumb: any correction you find yourself giving the agent twice belongs in this file permanently.
5. I read every line I ship. This is the non-negotiable one, and it is the whole ethic. I own the code with my name on the commit. The agent is a very fast junior who never gets tired and never gets defensive — but I am still the senior signing off, and sign-off means understanding. If I cannot explain a line to a teammate, it does not merge. Not "it passed the tests so it is probably fine." If I do not understand it, I do not ship it. That rule has never once cost me more than it saved.
People want a number, so here is my honest one, with the caveat that it is a feel, not a study. On well-scoped, mechanical, well-tested work — migrations, glue, boilerplate, first-pass review — I move maybe two to three times faster. On genuinely novel design work, the kind where the hard part is deciding what to build, the speedup rounds to zero, and if I am not careful it goes negative, because I can waste an afternoon coaxing an agent toward an answer I would have reached faster with a whiteboard.
The trap is that the mechanical work is loud and visible, so it feels like the agent made me fast everywhere. It did not. It made me fast on the 60% of my week that was never the interesting part. That is still a fantastic trade — but be clear-eyed about where the win comes from, or you will over-trust the agent on exactly the work it is worst at.
There is a second, quieter cost worth naming: review load. When you can generate code faster than you can read it, the bottleneck moves to your attention. The engineers who get burned are the ones who let generation outrun review and start rubber-stamping. Faster typing is only a win if you can still fully review what got typed. Protect the review step like it is the product — because in this workflow, it is.
The real shift is not that I write less code, though I do. It is where my attention goes. I spend far less time on mechanical typing and API-doc archaeology, and far more on the parts that were always the hard part: the design, the edge cases, the judgment calls, the "should we even build this" conversation.
Agents are extraordinary at the middle of a task and mediocre at the two ends — figuring out what to build, and verifying it is actually right. Those ends are exactly where senior engineering has always lived. So the work did not get easier; it got concentrated. My day now has less filler and more of the genuinely hard stuff back to back, which is more productive and, honestly, more tiring. Nobody warns you about that part.
CLAUDE.md of house rules, and reading every line you ship.Agentic coding is a genuine force multiplier, but it multiplies your process, not your intent. Point it at a well-scoped task with tests as a contract, a living instructions file, and a human reading every line, and it is transformative — the best productivity change I have made in years. Point it at a vague prompt with no guardrails and it will cheerfully help you ship a mess faster than ever, with all the confidence and none of the correctness.
The engineers who win with AI coding agents are not the ones who trust them most. They are the ones who have built the tightest loop around them — who know exactly where the agent is brilliant, exactly where it lies, and never confuse throughput for judgment. The agent got fast. Staying the one in charge is still the job.