Programming·Updated May 10, 2026·17 min read·👁 15.6K views

Flutter & Dart AI Prompts: Build Production iOS & Android Apps in 2026

John Allick· AI Researcher
📖 2,350 words
Quick Summary

Complete AI prompt library for Flutter developers. Covers Riverpod state management, GoRouter navigation, authentication, REST API integration, Isar local database, animations, widget testing, and CI/CD with Fastlane and GitHub Actions.

#Flutter#Dart#iOS#Android#Mobile Development#Riverpod#AI Coding Prompts

Flutter in 2026: The Cross-Platform Native SDK

Flutter compiles to native ARM code and runs on iOS, Android, Web, and Desktop from a single Dart codebase. The developers building the smoothest Flutter apps in 2026 use Riverpod 2, GoRouter, Freezed, and Dio — and they use AI prompts that specify these libraries explicitly to avoid getting legacy patterns from pre-2023 training data.

AI + Flutter: The State Management Generation Gap

Flutter has gone through three distinct state management eras — setState, Provider/BLoC, and Riverpod 2 — and AI models are spread unevenly across all three. A model trained predominantly on 2020–2022 Flutter code will confidently generate ChangeNotifierProvider and plain ConsumerWidget without code generation: functional, but two major releases behind. Riverpod 2's @riverpod annotation with AsyncNotifier and build_runner code gen is the current production standard, and it requires an explicit instruction to get right.

The session prompt that prevents the most Flutter rework: "Use Riverpod 2 with riverpod_generator (@riverpod annotation, AsyncNotifier for async state), GoRouter 13+ for navigation, Dart 3 records and pattern matching, and Freezed for all data classes. No setState, no Provider package, no Navigator 1.0 push/pop." A second gap: widget rebuild optimization. Models almost never add const constructors or extract stateless subtrees into separate widgets without being asked. Add "minimize rebuilds with const constructors and select() on Riverpod providers" to every UI-focused prompt.

For the full model-by-model comparison covering Flutter alongside .NET, see the Cross-Platform & Enterprise AI Prompts guide.

1. Flutter Project Architecture with Riverpod

⌥ PROMPT
You are a senior Flutter architect using Flutter 3.24, Dart 3, and Riverpod 2 with code generation.

Design a production Flutter app architecture for a project management app:

Packages: riverpod + riverpod_generator + riverpod_annotation, go_router, dio, freezed + json_serializable, isar (local DB), flutter_secure_storage, local_auth, firebase_messaging

Folder structure (feature-first):
- lib/features/auth/: models/, providers/, repositories/, screens/, widgets/
- lib/features/projects/: same structure
- lib/features/tasks/: same structure
- lib/core/: api/ (Dio client), router/, theme/, utils/, widgets/

Deliver:
1. Full directory tree with one-sentence purpose per folder
2. Riverpod provider graph: which providers depend on which
3. GoRouter setup: routes, redirect guard checking authProvider, deep link support
4. Dio client setup: base URL from flavor, auth interceptor, retry interceptor
5. Flavor configuration: development, staging, production (different API URLs and Firebase projects)

2. Riverpod 2 Providers with Code Generation

⌥ PROMPT
You are a Riverpod 2 expert using riverpod_generator.

Build the task state management layer using Riverpod code generation:

Models (Freezed):
@freezed class Task: id, title, description, status (enum), priority (enum), dueDate, assignedTo (User?), projectId, createdAt, updatedAt

Providers:

@riverpod
Future<List<Task>> taskList(TaskListRef ref, {required String projectId, TaskFilter? filter}) {
  // fetch from repository, watch auth provider for invalidation
}

@riverpod
class TaskNotifier extends _$TaskNotifier {
  // createTask: optimistic insert → API → revert on failure
  // updateTask: optimistic patch → API → revert on failure
  // deleteTask: optimistic remove → API → restore on failure
}

Repository (taskRepository provider):
- All HTTP calls via Dio client provider
- Map API errors to domain exceptions (NetworkException, AuthException, ValidationException)
- Local cache: read from Isar, update from API, write-through on mutations

Output: Task Freezed model with all JSON annotations, taskListProvider, TaskNotifier, TaskRepository, and generated file references (.g.dart). Show how to override providers in tests.

3. GoRouter with Auth Guard

⌥ PROMPT
You are a GoRouter expert for Flutter applications.

Configure GoRouter for a Flutter SaaS app with:

Routes:
- /login, /register, /forgot-password (no auth required)
- /onboarding (authenticated, no org selected)
- / (home shell with bottom navigation: /home, /projects, /notifications, /profile)
- /projects/:projectId (nested in shell)
- /tasks/:taskId (nested in shell)
- /settings (nested in shell)

Auth redirect logic:
- unauthenticated user → always redirect to /login (except public marketing routes)
- authenticated, no organisation → redirect to /onboarding
- authenticated + organisation → allow through to requested route
- /login when authenticated → redirect to /home

Features:
- ShellRoute for bottom navigation (preserves scroll position per tab)
- Deep link support: app.example.com/tasks/123 → opens TaskDetailScreen
- Custom error page for unknown routes (not default 404)
- refreshListenable: GoRouter re-evaluates redirect when authProvider changes (Riverpod + Listenable adapter)

Output: router.dart with full route tree, auth redirect function, and bottom navigation shell widget.

4. Dio HTTP Client with Interceptors

⌥ PROMPT
You are a Flutter networking expert.

Build a production Dio client for a Flutter app:

Base client:
- Base URL from flavour config
- ConnectTimeout: 30s, ReceiveTimeout: 30s, SendTimeout: 30s
- JSON headers by default

Interceptors (in this order):
1. AuthInterceptor: inject Bearer token from flutter_secure_storage into every request. On 401: call refresh token endpoint, update stored token, retry original request (max 1 retry). On refresh failure: clear storage, redirect to login via GoRouter.
2. RetryInterceptor: on network errors (SocketException, TimeoutException), retry 3 times with exponential backoff (1s, 2s, 4s). Do NOT retry 4xx errors.
3. LoggingInterceptor: in debug mode only — log method, URL, headers (redact Authorization), response status, duration. In release: no logging.
4. ErrorInterceptor: map DioException to domain exceptions: NetworkException, ServerException(statusCode, message), AuthException, ValidationException(fieldErrors).

Repository layer:
- Never let Dio types leak into feature code
- All methods return Either<Failure, T> (dartz) or throw typed exceptions
- Parse response with Freezed .fromJson()

Output: dio_client.dart, each interceptor file, and one complete repository example (TaskRepository).

5. Isar Local Database for Offline Support

⌥ PROMPT
You are a Flutter offline-first expert using Isar database.

Implement offline-first data layer for a Flutter task app:

Isar schemas:
- TaskEntity: all task fields + syncStatus (enum: synced/pending/conflict) + locallyUpdatedAt
- ProjectEntity: similar
- SyncQueueEntity: entityType, entityId, operation (create/update/delete), payload (String JSON), createdAt, retryCount

Sync service:
- On mutation: write to Isar immediately (optimistic), add to SyncQueue
- Background sync: ConnectivityProvider watches network; on reconnect, process SyncQueue
- Process queue: send each item to API, on success update syncStatus=synced + remove from queue
- On conflict: server returns 409 with server version → store in TaskEntity.syncStatus=conflict, notify user
- Periodic sync: every 60 seconds when app is active

Repository pattern:
- TaskRepository reads from Isar first (instant), then triggers background API refresh
- Use Isar watchers (watchObject/watchLazy) to push Isar changes to Riverpod providers reactively

Output: Isar schemas with @Collection annotation, SyncService, TaskRepository with Isar + API coordination, and ConnectivityProvider.

6. Flutter Widget & Integration Testing

⌥ PROMPT
You are a Flutter testing expert.

Write widget and integration tests for a TaskListScreen:

Widget tests (test/features/tasks/task_list_screen_test.dart):
Setup:
- ProviderScope with overrides: mock taskListProvider, mock authProvider
- Use pumpWidget with MaterialApp.router(routerConfig: mockGoRouter)

Test cases:
1. Shows loading indicator while taskListProvider is AsyncLoading
2. Shows task list when loaded — verify task titles with find.text
3. Shows empty state widget when task list is empty
4. Shows error widget with retry button when provider is AsyncError
5. Tap task row → verify GoRouter.go called with correct route
6. Pull-to-refresh → verify provider.invalidate() called
7. FAB tap → verify navigation to task creation screen

Integration tests (integration_test/task_flow_test.dart):
- Full flow: app launch → login (mock API) → create project → create task → mark complete
- Use integrationDriver with real Isar (in-memory) and mocked Dio

Golden tests:
- TaskListScreen with 5 tasks → compare to baseline PNG

Output: widget test file, integration test file, test helpers (pumpApp wrapper, mock providers).

7. CI/CD with Fastlane & GitHub Actions

⌥ PROMPT
You are a Flutter DevOps engineer.

Build a CI/CD pipeline for a Flutter app using GitHub Actions and Fastlane:

GitHub Actions workflow (.github/workflows/flutter.yml):

test job (all PRs):
- Flutter 3.24 stable setup with cache
- flutter pub get
- dart format --set-exit-if-changed (fail on unformatted code)
- flutter analyze (zero warnings)
- flutter test --coverage (fail below 75%)
- Upload coverage to Codecov

build-android job (main branch):
- flutter build appbundle --release --flavor production
- Sign with keystore from GitHub Secrets
- Upload .aab artifact

build-ios job (main branch, macos-latest runner):
- flutter build ipa --release --flavor production --export-options-plist
- Code sign via Fastlane match (App Store Connect API key from Secrets)
- Upload .ipa artifact

deploy job (after both build jobs):
- Android: Fastlane supply upload to Google Play internal track
- iOS: Fastlane pilot upload to TestFlight
- Slack notification with build number and download link

Fastlane (fastlane/Fastfile):
- lane :test: flutter test
- lane :beta_android: build + Play Store internal
- lane :beta_ios: build + TestFlight
- lane :release: promote internal → production on both stores

Output: GitHub Actions YAML, Fastfile, Matchfile, and required GitHub Secrets list.

End-to-End Workflow: Feature Screen with Data

Building a task detail screen end-to-end with Flutter + Riverpod + GoRouter:

  1. Route: "Add /tasks/:id to GoRouter configuration. Route guard: redirect to /login if not authenticated (read from authProvider). Pass taskId as path parameter to TaskDetailScreen."
  2. Provider (Prompt 2 variant): "Create a taskDetailProvider(String taskId) using @riverpod code generation: AsyncNotifier that fetches GET /tasks/{taskId} via Dio, exposes refresh(), and updates the task list provider's cache on successful edit."
  3. Screen (Prompt 3 variant): "Create TaskDetailScreen as a ConsumerWidget: use ref.watch(taskDetailProvider(taskId)), show CircularProgressIndicator while loading, ErrorWidget with retry on error, task data with Reanimated-style AnimatedSwitcher on state change."
  4. Mutation: "Add completeTask() method to taskDetailProvider: PATCH /tasks/{taskId}/complete, optimistic update via state = AsyncData(task.copyWith(status: 'completed')), revert on error."
  5. Tests (Prompt 7 variant): "Write flutter_test widget tests for TaskDetailScreen: override taskDetailProvider with ProviderScope overrides, test loading state, error state with retry callback, completed state shows completion checkmark."

Where AI Goes Wrong in Flutter

  • StatefulWidget + setState instead of Riverpod. AI defaults to the simplest state management approach. For anything beyond trivial local state, setState doesn't scale and leads to prop drilling and redundant rebuilds. Specify "Riverpod 2 with @riverpod code generation" explicitly.
  • Navigator.push() instead of GoRouter. AI generates Navigator.push(context, MaterialPageRoute(...)). GoRouter supports deep links, URL-based routing, guards, and shell routes — essential for production apps. Specify "GoRouter 13+" in every navigation-related prompt.
  • Mutable data classes instead of Freezed. AI generates plain Dart classes with mutable fields. Riverpod works best with immutable data classes that have copyWith(). Freezed generates this automatically. Specify "use Freezed for all data models."
  • BuildContext across async gaps. AI generates code that uses context after an await without checking mounted. In Flutter, BuildContext is invalid after widget unmount — this throws in debug mode and silently misbehaves in release. Always check if (!mounted) return; after any await in a StatefulWidget.
  • Dio vs http package confusion. AI uses both in the same project, or generates raw http package code when Dio with interceptors is specified. Pick one network library and specify it explicitly in every prompt.

8. Good vs Bad Flutter Prompts

Task❌ Bad Prompt✅ Good Prompt
State"Build a counter app with Riverpod""Create a Riverpod 2 AsyncNotifier (with riverpod_generator @riverpod annotation) for tasks: fetchTasks(), createTask() with optimistic insert + revert on API failure, deleteTask() with optimistic remove. Dart 3 strict null safety."
Navigation"Add navigation to my app""Configure GoRouter for Flutter: ShellRoute with bottom nav (home/projects/profile), auth redirect guard reading from Riverpod authProvider via refreshListenable, deep link /tasks/:id, and custom 404 page. Show how redirect is re-evaluated on login/logout."
API"Call an API in Flutter""Build a Dio client with AuthInterceptor (inject Bearer token, refresh on 401 with max 1 retry), RetryInterceptor (exponential backoff on network errors, skip 4xx), and ErrorInterceptor mapping DioException to typed domain exceptions. Never let Dio types leak into feature code."

Before You Prompt: Flutter Context Setup

Flutter's ecosystem moves fast — Riverpod 1.0 vs 2.0 with code generation, GoRouter vs Navigator 1.0, and Freezed vs manual models are all active in training data. Without version pinning, AI generates patterns from whichever version was most common at training time. This block prevents the most frequent mismatches:

⌥ PROMPT
Context for all Flutter prompts in this session:
- SDK: Flutter 3.29+ / Dart 3.7, null safety strict mode
- State management: Riverpod 2 with code generation
  Use @riverpod annotation + riverpod_generator (NOT ConsumerWidget + provider.watch without annotation)
  Never: Provider package, BLoC (unless existing codebase), setState for business logic
- Navigation: GoRouter v14 with typed routes
  Never: Navigator.push() / Navigator.of(context).push()
- Data models: Freezed + json_serializable (immutable, copyWith, fromJson/toJson)
- HTTP client: Dio 5 with interceptors (auth token injection, 401 refresh, logging)
- Secure storage: flutter_secure_storage (NEVER SharedPreferences for auth tokens)
- Tests: flutter_test + mocktail

The Riverpod annotation syntax is critical: without @riverpod and the build_runner generation step, Riverpod 2's type safety features don't apply. AI sometimes generates Riverpod 1 syntax (Provider<T>) or Riverpod 2 without annotations — both produce worse type inference. Mention "generated providers with riverpod_generator" in every state management prompt.

3 Common Mistakes When Prompting AI for Flutter

Mistake 1: Business logic in StatefulWidget with setState

Asking for "a Flutter screen that fetches and displays user data" produces a StatefulWidget that calls the API in initState() and stores data in setState(). This approach is untestable, doesn't share state across screens, and triggers full widget tree rebuilds. Specify: "Use a Riverpod @riverpod AsyncNotifier for data fetching — the widget is a ConsumerWidget that watches the provider. No StatefulWidget needed." The resulting code is half the size and fully testable.

Mistake 2: SharedPreferences for authentication tokens

AI generates SharedPreferences.getInstance().then(prefs => prefs.setString('token', jwt)) because SharedPreferences is the simplest storage API and dominates examples in training data. SharedPreferences data is readable by any app on a rooted Android device. Tokens stored this way can be extracted by malware. Specify: "store auth tokens in flutter_secure_storage using the Keychain (iOS) / Android Keystore (Android) — never SharedPreferences for any security-sensitive value."

Mistake 3: Navigator.push() bypassing GoRouter's redirect guard

GoRouter's authentication redirect guard works by intercepting all navigation events through the router. Calling Navigator.push() or Navigator.of(context).push() directly bypasses the router entirely — unauthenticated users can navigate to protected screens. Even if your context block says "GoRouter," some AI-generated screens still use Navigator.push for in-screen navigation. Audit every generated navigation call and replace with context.push('route') from GoRouter.

Further Reading

Resources for AI-assisted Flutter development:

Generate a custom Flutter prompt → Try PromptPrepare free

Help & Answers

Frequently Asked Questions

John AllickAI Researcher· Updated May 10, 2026

John Allick is an AI researcher specializing in prompt engineering and large language model evaluation. He benchmarks models across ChatGPT, Claude, Gemini, Grok, and DeepSeek, focusing on practical techniques that produce reliable, production-ready outputs. Every guide on PromptPrepare is tested live on current model versions before publication.

✓ Expert-tested on live models✓ Updated May 10, 2026✓ Model-verified examples

Found this helpful?

Save it to your library or share with your team.

Keep Reading

Related Guides

Apply this guide instantly

Free AI prompt generator