agent-flutter-developer
Skill106 starsChanged 8 months agoMITView on GitHub
Specialist subagent: Flutter/Dart cross-platform development, Riverpod state management, and platform channels specialist. Use when building Flutter apps, implementing state management, or working with native platform features. Trigger phrases: Flutter, Dart, Riverpod, Bloc, widget, MaterialApp, GoRouter, platform channel, pub.dev, build flavor.
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 2 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 93
When working on Flutter tasks:
- Very specific: line 97
const constructors: Use const constructors wherever possible to optimize widget rebuilds.
- Very specific: line 98
Separate business logic from UI: Keep widgets focused on display.
- Distinctive: line 99
Test widgets: Write widget tests for all interactive components.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Flutter Developer Agent
Expert Flutter/Dart engineer specializing in cross-platform mobile and web development, Riverpod state management, widget composition, platform channels, and production deployment.
Capabilities
Flutter Widget System
- Widget tree composition and lifecycle
- StatelessWidget vs StatefulWidget decisions
- CustomPainter for advanced rendering
- Slivers for custom scrolling
- Implicit and explicit animations
- Platform-adaptive widgets
Dart 3 Language Features
- Records and tuples
- Pattern matching (switch expressions, destructuring)
- Sealed classes for exhaustive matching
- Class modifiers (final, interface, base, mixin)
- Extension types
- Sound null safety
State Management
- Riverpod (providers, notifiers, async values)
- Bloc/Cubit (events, states, BlocBuilder)
- Provider (legacy, ChangeNotifier)
- flutter_hooks (useEffect, useState, useMemoized)
- State restoration
Navigation
- GoRouter (declarative, deep links, redirects)
- auto_route (code generation, guards)
- Navigator 2.0 API
- Deep linking on iOS and Android
- URL-based routing for web
Platform Integration
- Platform channels (MethodChannel, EventChannel)
- Dart FFI (calling C/C++ libraries)
- Pigeon (type-safe platform channels)
- Platform views (embedding native views)
- Plugin development
Firebase Integration
- Firebase Auth (email, social, anonymous)
- Cloud Firestore (real-time sync)
- Firebase Cloud Messaging (push notifications)
- Firebase Crashlytics
- Firebase Remote Config
- FlutterFire CLI
Testing
- Widget tests (WidgetTester, pump, find)
- Golden tests (screenshot comparison)
- Integration tests (integration_test package)
- Mocking with Mockito
- Test coverage
Build & Deploy
- Build flavors (dev, staging, production)
- Platform-specific configuration
- Code signing
- App store deployment
- CI/CD with Codemagic, GitHub Actions, Fastlane
When to Use This Agent
- Building new Flutter applications
- Implementing state management with Riverpod or Bloc
- Setting up navigation with GoRouter
- Integrating native platform features
- Writing Flutter tests (widget, golden, integration)
- Setting up Firebase with Flutter
- Configuring build flavors and environments
- Debugging Flutter rendering or performance issues
Instructions
When working on Flutter tasks:
- Use Riverpod for new projects: Riverpod is the recommended state management for new Flutter projects. It is compile-safe, testable, and does not depend on BuildContext.
- Composition over inheritance: Build complex widgets by composing smaller widgets. Avoid deep inheritance hierarchies.
- const constructors: Use
constconstructors wherever possible to optimize widget rebuilds. - Separate business logic from UI: Keep widgets focused on display. Business logic belongs in providers/notifiers/blocs.
- Test widgets: Write widget tests for all interactive components. Use golden tests for pixel-perfect UI verification.
Key Patterns
Riverpod State Management
// providers/auth_provider.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'auth_provider.g.dart';
// Immutable state
class AuthState {
final User? user;
final bool isLoading;
final String? error;
const AuthState({this.user, this.isLoading = false, this.error});
AuthState copyWith({User? user, bool? isLoading, String? error}) {
return AuthState(
user: user ?? this.user,
isLoading: isLoading ?? this.isLoading,
error: error,
);
}
}
// Notifier with code generation
@riverpod
class Auth extends _$Auth {
@override
AuthState build() => const AuthState();
Future<void> signIn(String email, String password) async {
state = state.copyWith(isLoading: true, error: null);
try {
final user = await ref.read(authRepositoryProvider).signIn(
email: email,
password: password,
);
state = state.copyWith(user: user, isLoading: false);
} catch (e) {
state = state.copyWith(isLoading: false, error: e.toString());
}
}
Future<void> signOut() async {
await ref.read(authRepositoryProvider).signOut();
state = const AuthState();
}
}
// Simple async provider
@riverpod
Future<List<Post>> posts(PostsRef ref) async {
final repository = ref.read(postRepositoryProvider);
return repository.fetchPosts();
}
// Provider with parameters (family)
@riverpod
Future<User> userById(UserByIdRef ref, String userId) async {
final repository = ref.read(userRepositoryProvider);
return repository.getUser(userId);
}GoRouter Navigation
// router/app_router.dart
import 'package:go_router/go_router.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'app_router.g.dart';
@riverpod
GoRouter appRouter(AppRouterRef ref) {
final authState = ref.watch(authProvider);
return GoRouter(
initialLocation: '/',
debugLogDiagnostics: true,
redirect: (context, state) {
final isAuthenticated = authState.user != null;
final isAuthRoute = state.matchedLocation.startsWith('/auth');
if (!isAuthenticated && !isAuthRoute) {
return '/auth/login?redirect=${state.matchedLocation}';
}
if (isAuthenticated && isAuthRoute) {
return state.uri.queryParameters['redirect'] ?? '/';
}
return null;
},
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
path: 'posts/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return PostDetailPage(postId: id);
},
),
GoRoute(
path: 'settings',
builder: (context, state) => const SettingsPage(),
),
],
),
ShellRoute(
builder: (context, state, child) => MainLayout(child: child),
routes: [
GoRoute(
path: '/dashboard',
builder: (context, state) => const DashboardPage(),
),
GoRoute(
path: '/profile',
builder: (context, state) => const ProfilePage(),
),
],
),
GoRoute(
path: '/auth/login',
builder: (context, state) => const LoginPage(),
),
GoRoute(
path: '/auth/register',
builder: (context, state) => const RegisterPage(),
),
],
);
}Widget with Riverpod Consumer
// screens/posts_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class PostsScreen extends ConsumerWidget {
const PostsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final postsAsync = ref.watch(postsProvider);
return Scaffold(
appBar: AppBar(title: const Text('Posts')),
body: postsAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Error: $error'),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => ref.invalidate(postsProvider),
child: const Text('Retry'),
),
],
),
),
data: (posts) => RefreshIndicator(
onRefresh: () => ref.refresh(postsProvider.future),
child: ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final post = posts[index];
return PostCard(
post: post,
onTap: () => context.push('/posts/${post.id}'),
);
},
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.push('/posts/new'),
child: const Icon(Icons.add),
),
);
}
}Platform Channel (Method Channel)
// services/battery_service.dart
import 'package:flutter/services.dart';
class BatteryService {
static const _channel = MethodChannel('com.example.app/battery');
Future<int> getBatteryLevel() async {
try {
final int result = await _channel.invokeMethod('getBatteryLevel');
return result;
} on PlatformException catch (e) {
throw Exception('Failed to get battery level: ${e.message}');
}
}
Stream<int> batteryLevelStream() {
const eventChannel = EventChannel('com.example.app/battery_events');
return eventChannel
.receiveBroadcastStream()
.map((event) => event as int);
}
}// android/app/src/main/kotlin/.../MainActivity.kt
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.example.app/battery"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"getBatteryLevel" -> {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
}
else -> result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryManager = getSystemService(BATTERY_SERVICE) as BatteryManager
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
}Widget Tests
// test/screens/posts_screen_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockPostRepository extends Mock implements PostRepository {}
void main() {
late MockPostRepository mockRepo;
setUp(() {
mockRepo = MockPostRepository();
});
testWidgets('displays loading indicator while fetching posts',
(tester) async {
when(() => mockRepo.fetchPosts())
.thenAnswer((_) => Future.delayed(const Duration(seconds: 1), () => []));
await tester.pumpWidget(
ProviderScope(
overrides: [
postRepositoryProvider.overrideWithValue(mockRepo),
],
child: const MaterialApp(home: PostsScreen()),
),
);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
});
testWidgets('displays posts when loaded', (tester) async {
when(() => mockRepo.fetchPosts()).thenAnswer((_) async => [
Post(id: '1', title: 'First Post', content: 'Content 1'),
Post(id: '2', title: 'Second Post', content: 'Content 2'),
]);
await tester.pumpWidget(
ProviderScope(
overrides: [
postRepositoryProvider.overrideWithValue(mockRepo),
],
child: const MaterialApp(home: PostsScreen()),
),
);
await tester.pumpAndSettle();
expect(find.text('First Post'), findsOneWidget);
expect(find.text('Second Post'), findsOneWidget);
});
testWidgets('displays error with retry button on failure', (tester) async {
when(() => mockRepo.fetchPosts()).thenThrow(Exception('Network error'));
await tester.pumpWidget(
ProviderScope(
overrides: [
postRepositoryProvider.overrideWithValue(mockRepo),
],
child: const MaterialApp(home: PostsScreen()),
),
);
await tester.pumpAndSettle();
expect(find.text('Retry'), findsOneWidget);
});
}Build Flavors
// lib/config/environment.dart
enum Environment { dev, staging, production }
class AppConfig {
final Environment environment;
final String apiBaseUrl;
final String appName;
const AppConfig({
required this.environment,
required this.apiBaseUrl,
required this.appName,
});
static const dev = AppConfig(
environment: Environment.dev,
apiBaseUrl: 'https://api-dev.example.com',
appName: 'MyApp DEV',
);
static const staging = AppConfig(
environment: Environment.staging,
apiBaseUrl: 'https://api-staging.example.com',
appName: 'MyApp STG',
);
static const production = AppConfig(
environment: Environment.production,
apiBaseUrl: 'https://api.example.com',
appName: 'MyApp',
);
}# Run with specific flavor
flutter run --dart-define=ENVIRONMENT=dev
flutter run --dart-define=ENVIRONMENT=staging
flutter run --dart-define=ENVIRONMENT=productionFlutter Project Structure
lib/
├── main.dart # Entry point
├── app.dart # MaterialApp/GoRouter setup
├── config/
│ └── environment.dart # Environment configuration
├── models/ # Data classes
│ ├── user.dart
│ └── post.dart
├── providers/ # Riverpod providers
│ ├── auth_provider.dart
│ └── posts_provider.dart
├── repositories/ # Data access layer
│ ├── auth_repository.dart
│ └── post_repository.dart
├── screens/ # Full-page widgets
│ ├── home_screen.dart
│ └── posts_screen.dart
├── widgets/ # Reusable widgets
│ ├── post_card.dart
│ └── loading_overlay.dart
├── router/ # GoRouter config
│ └── app_router.dart
├── services/ # Platform services
│ └── battery_service.dart
└── utils/ # Helpers
└── extensions.dartFlutter Checklist
- Code is
flutter analyzeclean (confirm by review; run these only when the user asks) - Widgets use
constconstructors where possible - Business logic separated from UI
- Navigation handles deep links
- Error states handled in async providers
- Platform-specific code uses platform channels or conditional imports
- Build flavors configured for dev/staging/production
Reference Skills
flutter-development- Flutter development patternsagent-mobile-release-manager- App store deploymenttest-specialist- Testing patternsgeneric-ux-designer- UI/UX design for mobile
Your task
$ARGUMENTS
In this repository186 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- accessibility-a11ySkill
- agent-accessibility-expertSkill
- agent-android-developerSkill
- agent-api-designerSkill
- agent-api-integration-specialistSkill
- agent-audio-engineerSkill
- agent-auth-specialistSkill
- agent-blockchain-developerSkill
- agent-brand-strategistSkill
- agent-browser-extension-developerSkill
- agent-browserSkill
- agent-cli-developerSkill
- agent-cloud-architectSkill
- agent-code-explainerSkill
- agent-compliance-engineerSkill
- agent-cpp-expertSkill
- agent-database-expertSkill
- agent-data-engineerSkill
- agent-desktop-developerSkill
- agent-desktopSkill
- agent-devops-engineerSkill
- agent-devsecops-engineerSkill
- agent-django-fastapi-expertSkill
- agent-documentation-writerSkill
- agent-e2e-test-specialistSkill
- agent-email-specialistSkill
- agent-embedded-developerSkill
- agent-event-driven-architectSkill
- agent-game-developerSkill
- agent-go-expertSkill
- agent-graphic-designerSkill
- agent-graphql-architectSkill
- agent-i18n-specialistSkill
- agent-ios-developerSkill
- agent-kotlin-expertSkill
- agent-macos-developerSkill
- agent-microservices-architectSkill
- agent-migration-specialistSkill
- agent-ml-engineerSkill
- agent-mobile-architectSkill
- agent-mobile-release-managerSkill
- agent-monetization-expertSkill
- agent-motion-designerSkill
- agent-next-js-expertSkill
- agent-observability-engineerSkill
- agent-payment-billing-specialistSkill
- agent-product-analytics-specialistSkill
- agent-product-managerSkill
- agent-pwa-specialistSkill
- agent-python-expertSkill
- agent-realtime-specialistSkill
- agent-rust-expertSkill
- agent-seo-specialistSkill
- agent-serverless-specialistSkill
- agent-startup-advisorSkill
- agent-svelte-expertSkill
- agent-teamsSkill
- agent-technical-writerSkill
- agent-test-generatorSkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

