Agent skill

flutter-development

Cross-platform development with Flutter and Dart for iOS, Android, Web, Desktop, and embedded. Use when building Flutter apps, implementing Material/Cupertino design, or optimizing Dart code.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/flutter-development

SKILL.md

Flutter Development

Build beautiful, natively compiled applications for mobile, web, desktop, and embedded from a single codebase.

Supported Platforms

Platform Status Notes
iOS Stable Full native performance
Android Stable Full native performance
Web Stable PWA support
macOS Stable Native desktop
Windows Stable Native desktop
Linux Stable Native desktop

Project Structure

my_app/
├── lib/
│   ├── main.dart
│   ├── app.dart
│   ├── features/
│   │   ├── home/
│   │   │   ├── home_screen.dart
│   │   │   ├── home_controller.dart
│   │   │   └── widgets/
│   │   └── settings/
│   ├── core/
│   │   ├── theme/
│   │   ├── utils/
│   │   └── constants/
│   └── shared/
│       ├── models/
│       ├── services/
│       └── widgets/
├── test/
├── pubspec.yaml
└── analysis_options.yaml

Basic Structure

Main Entry

dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const HomeScreen(),
    );
  }
}

Stateless Widget

dart
class GreetingCard extends StatelessWidget {
  const GreetingCard({
    super.key,
    required this.name,
  });

  final String name;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(
          'Hello, $name!',
          style: Theme.of(context).textTheme.headlineMedium,
        ),
      ),
    );
  }
}

Stateful Widget

dart
class Counter extends StatefulWidget {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text(
          'Count: $_count',
          style: Theme.of(context).textTheme.headlineLarge,
        ),
        const SizedBox(height: 16),
        ElevatedButton(
          onPressed: _increment,
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

State Management

Riverpod (Recommended)

dart
// Provider definition
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
  return CounterNotifier();
});

class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0);

  void increment() => state++;
  void decrement() => state--;
}

// Usage in widget
class CounterScreen extends ConsumerWidget {
  const CounterScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);

    return Scaffold(
      body: Center(
        child: Text('Count: $count'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

// Async provider
final itemsProvider = FutureProvider<List<Item>>((ref) async {
  final repository = ref.watch(repositoryProvider);
  return repository.fetchItems();
});

BLoC Pattern

dart
// Events
abstract class CounterEvent {}
class IncrementPressed extends CounterEvent {}
class DecrementPressed extends CounterEvent {}

// State
class CounterState {
  final int count;
  const CounterState(this.count);
}

// BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(const CounterState(0)) {
    on<IncrementPressed>((event, emit) {
      emit(CounterState(state.count + 1));
    });
    on<DecrementPressed>((event, emit) {
      emit(CounterState(state.count - 1));
    });
  }
}

// Usage
BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) {
    return Text('Count: ${state.count}');
  },
)

Navigation

GoRouter (Recommended)

dart
final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
      routes: [
        GoRoute(
          path: 'details/:id',
          builder: (context, state) {
            final id = state.pathParameters['id']!;
            return DetailsScreen(id: id);
          },
        ),
      ],
    ),
    GoRoute(
      path: '/settings',
      builder: (context, state) => const SettingsScreen(),
    ),
  ],
);

// Usage
MaterialApp.router(
  routerConfig: router,
)

// Navigate
context.go('/details/123');
context.push('/settings');
context.pop();

Common Widgets

Lists

dart
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    final item = items[index];
    return ListTile(
      leading: CircleAvatar(
        backgroundImage: NetworkImage(item.imageUrl),
      ),
      title: Text(item.title),
      subtitle: Text(item.subtitle),
      trailing: const Icon(Icons.chevron_right),
      onTap: () => context.push('/details/${item.id}'),
    );
  },
)

Forms

dart
class LoginForm extends StatefulWidget {
  const LoginForm({super.key});

  @override
  State<LoginForm> createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  void _submit() {
    if (_formKey.currentState!.validate()) {
      // Process login
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            controller: _emailController,
            decoration: const InputDecoration(
              labelText: 'Email',
              prefixIcon: Icon(Icons.email),
            ),
            keyboardType: TextInputType.emailAddress,
            validator: (value) {
              if (value == null || !value.contains('@')) {
                return 'Enter a valid email';
              }
              return null;
            },
          ),
          const SizedBox(height: 16),
          TextFormField(
            controller: _passwordController,
            decoration: const InputDecoration(
              labelText: 'Password',
              prefixIcon: Icon(Icons.lock),
            ),
            obscureText: true,
            validator: (value) {
              if (value == null || value.length < 6) {
                return 'Password must be at least 6 characters';
              }
              return null;
            },
          ),
          const SizedBox(height: 24),
          ElevatedButton(
            onPressed: _submit,
            child: const Text('Login'),
          ),
        ],
      ),
    );
  }
}

Networking

Dio HTTP Client

dart
class ApiService {
  final Dio _dio;

  ApiService() : _dio = Dio(BaseOptions(
    baseUrl: 'https://api.example.com',
    connectTimeout: const Duration(seconds: 10),
    receiveTimeout: const Duration(seconds: 10),
  )) {
    _dio.interceptors.add(LogInterceptor());
  }

  Future<List<Item>> getItems() async {
    try {
      final response = await _dio.get('/items');
      return (response.data as List)
          .map((json) => Item.fromJson(json))
          .toList();
    } on DioException catch (e) {
      throw ApiException(e.message ?? 'Unknown error');
    }
  }
}

Local Storage

Hive (Recommended)

dart
// Model
@HiveType(typeId: 0)
class Item extends HiveObject {
  @HiveField(0)
  late String id;

  @HiveField(1)
  late String name;
}

// Setup
await Hive.initFlutter();
Hive.registerAdapter(ItemAdapter());
await Hive.openBox<Item>('items');

// Usage
final box = Hive.box<Item>('items');
await box.put('key', item);
final item = box.get('key');

SharedPreferences

dart
final prefs = await SharedPreferences.getInstance();
await prefs.setString('token', 'abc123');
final token = prefs.getString('token');

Platform-Specific Code

dart
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;

Widget build(BuildContext context) {
  if (kIsWeb) {
    return WebLayout();
  } else if (Platform.isIOS) {
    return CupertinoLayout();
  } else if (Platform.isAndroid) {
    return MaterialLayout();
  } else if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) {
    return DesktopLayout();
  }
  return DefaultLayout();
}

Testing

Widget Tests

dart
testWidgets('Counter increments', (tester) async {
  await tester.pumpWidget(const MaterialApp(home: Counter()));

  expect(find.text('Count: 0'), findsOneWidget);

  await tester.tap(find.byIcon(Icons.add));
  await tester.pump();

  expect(find.text('Count: 1'), findsOneWidget);
});

Unit Tests

dart
test('Item fromJson parses correctly', () {
  final json = {'id': '1', 'name': 'Test'};
  final item = Item.fromJson(json);

  expect(item.id, '1');
  expect(item.name, 'Test');
});

Performance

Best Practices

dart
// Use const constructors
const SizedBox(height: 16)

// Avoid rebuilds with const
const MyStaticWidget()

// Use ListView.builder for long lists
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) => ItemWidget(items[index]),
)

// Cache images
CachedNetworkImage(
  imageUrl: url,
  placeholder: (context, url) => const CircularProgressIndicator(),
)

Best Practices

DO:

  • Use const constructors everywhere possible
  • Split widgets into smaller components
  • Use Riverpod or BLoC for state
  • Follow Flutter naming conventions
  • Write widget tests

DON'T:

  • Put logic in build methods
  • Create god widgets
  • Use setState for complex state
  • Ignore null safety
  • Skip code generation setup

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results