Skip to content
Back to skills

Agent Flutter Developer

ASecurity

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.

  • 100 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 29, 2026
ai-agentsgokotlinc++shellbashexpresstestingdebugginggitapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 30, 2026

npx -y skills add travisjneuman/.claude --skill agent-flutter-developer --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Agent Flutter Developer?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Agent Flutter Developer
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/travisjneuman-agent-flutter-developer/badge)](https://www.skillsdirectory.com/skills/travisjneuman-agent-flutter-developer)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: agent-flutter-developer
description: "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."
context: fork
agent: general-purpose
---
# 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:

1. **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.
2. **Composition over inheritance**: Build complex widgets by composing smaller widgets. Avoid deep inheritance hierarchies.
3. **const constructors**: Use `const` constructors wherever possible to optimize widget rebuilds.
4. **Separate business logic from UI**: Keep widgets focused on display. Business logic belongs in providers/notifiers/blocs.
5. **Test widgets**: Write widget tests for all interactive components. Use golden tests for pixel-perfect UI verification.

## Key Patterns

### Riverpod State Management

```dart
// 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

```dart
// 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

```dart
// 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)

```dart
// 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);
  }
}
```

```kotlin
// 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

```dart
// 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

```dart
// 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',
  );
}
```

```bash
# Run with specific flavor
flutter run --dart-define=ENVIRONMENT=dev
flutter run --dart-define=ENVIRONMENT=staging
flutter run --dart-define=ENVIRONMENT=production
```

## Flutter 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.dart
```

## Flutter Checklist

- [ ] Code is `flutter analyze` clean (confirm by review; run these only when the user asks)
- [ ] Widgets use `const` constructors 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 patterns
- `agent-mobile-release-manager` - App store deployment
- `test-specialist` - Testing patterns
- `generic-ux-designer` - UI/UX design for mobile

## Your task

$ARGUMENTS

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…