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 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
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
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
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
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
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
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
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:
- 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."
- 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."
- 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."
- Mutation: "Add completeTask() method to taskDetailProvider: PATCH /tasks/{taskId}/complete, optimistic update via state = AsyncData(task.copyWith(status: 'completed')), revert on error."
- 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
contextafter anawaitwithout checkingmounted. In Flutter, BuildContext is invalid after widget unmount — this throws in debug mode and silently misbehaves in release. Always checkif (!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:
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:
- How engineering teams ship mobile apps faster with AI — AI strategies for Flutter projects from architecture to App Store submission
- How to write the perfect AI prompt — the underlying framework for every prompt template in this guide
- AI Coding Prompt Library — 50+ copy-ready prompts for Flutter, Dart, Riverpod, GoRouter, and mobile testing
Generate a custom Flutter prompt → Try PromptPrepare free
Help & Answers
Frequently Asked Questions
Found this helpful?
Save it to your library or share with your team.
Keep Reading