一键导入
flutter-dart
Flutter, Dart, widgets, Riverpod. Use when working on flutter-dart tasks, related files, debugging, implementation, review, or verification workflows.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Flutter, Dart, widgets, Riverpod. Use when working on flutter-dart tasks, related files, debugging, implementation, review, or verification workflows.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Framework-agnostic website security audit and authorized pentest methodology (OWASP, red-team attacker mindset, white-hat reporting). Use for web pentest, vulnerability assessment, attack-surface review, bug bounty prep, or security audit of any web stack.
Critical path identification, risk-adjusted effort sizing, complexity detection, and execution strategy selection. Use when scoping a task, deciding direct-vs-planned execution, breaking down large work, or assessing how many steps something really needs.
Multi-phase workflows, state machines, DAG task execution, checkpoint/resume, and saga patterns for complex multi-step or multi-session work. Use when a task spans 5+ steps, multiple files, multiple sessions, or has irreversible phases.
Understanding legacy and unfamiliar code, git blame/history analysis, dependency archaeology, and reconstructing intent behind existing code. Use when working in unfamiliar codebases, investigating "why was this written this way", or before changing code you didn't write.
Retry strategies, rollback protocols, circuit breakers for delegations, escalation chains, and failure budgets. Use when verification fails, a fix doesn't work, a sub-agent returns weak output, or you are stuck in a failure loop.
Production incident triage, rollback procedures, blast radius assessment, severity classification, and post-mortem templates. Use when production is down, a deploy broke something, users are impacted, or you need to stabilize before fixing root cause.
| name | flutter-dart |
| description | Flutter, Dart, widgets, Riverpod. Use when working on flutter-dart tasks, related files, debugging, implementation, review, or verification workflows. |
Trigger this skill when:
.dart, pubspec.yaml, analysis_options.yamlflutter CLI, dart CLI, build_runnerWidget, StatelessWidget, StatefulWidget, ConsumerWidgetWhat state do you need?
+-- Local widget state (counter, toggle)?
| +-- StatefulWidget + setState (simple)
| +-- Hooks (flutter_hooks) for reusable logic
+-- Shared app state?
| +-- Riverpod 3 (recommended — compile-safe, testable)
| +-- Bloc (enterprise, event-driven, verbose)
| +-- Provider (legacy — migrate to Riverpod)
+-- Server/async data?
| +-- Riverpod AsyncNotifier / FutureProvider
| +-- Bloc + Repository pattern
+-- Form state?
| +-- flutter_form_builder or reactive_forms
+-- Navigation state?
+-- GoRouter (declarative, deep linking)
+-- auto_route (code generation)
How to structure the app?
+-- Small app (< 10 screens)? -> Feature folders + Riverpod
+-- Medium app? -> Clean Architecture (data/domain/presentation)
+-- Large team? -> Feature-first modules + dependency injection
+-- Need offline-first? -> Repository pattern + local DB (Drift/Isar)
// Dart 3.6 — sealed classes for exhaustive pattern matching
sealed class Result<T> {
const Result();
}
class Success<T> extends Result<T> {
final T value;
const Success(this.value);
}
class Failure<T> extends Result<T> {
final AppError error;
const Failure(this.error);
}
// Exhaustive switch — compiler enforces all cases handled
Widget buildResult(Result<User> result) => switch (result) {
Success(:final value) => UserCard(user: value),
Failure(:final error) => ErrorWidget(error: error),
};
// Records — lightweight tuples with named fields
typedef UserWithPosts = ({User user, List<Post> posts});
Future<UserWithPosts> fetchUserData(String id) async {
final (user, posts) = await (
userRepo.find(id),
postRepo.findByUser(id),
).wait; // Parallel futures
return (user: user, posts: posts);
}
// Pattern matching in if-case
if (json case {'user': {'name': String name, 'age': int age}}) {
print('$name is $age years old');
}
// Extension types — zero-cost wrappers (Dart 3.3+)
extension type UserId(String value) {
UserId.generate() : this(const Uuid().v4());
bool get isValid => value.isNotEmpty;
}
extension type Email(String value) {
bool get isValid => value.contains('@');
}
// Class modifiers
final class AppConfig { // Cannot be extended or implemented
final String apiUrl;
final Duration timeout;
const AppConfig({required this.apiUrl, this.timeout = const Duration(seconds: 30)});
}
interface class Repository<T> { // Can only be implemented, not extended
Future<T?> find(String id);
Future<T> save(T entity);
Future<void> delete(String id);
}
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'providers.g.dart';
// Code-generated providers (Riverpod 3 — annotation-based)
@riverpod
class AuthNotifier extends _$AuthNotifier {
@override
AuthState build() => const AuthState.unauthenticated();
Future<void> login(String email, String password) async {
state = const AuthState.loading();
try {
final user = await ref.read(authRepositoryProvider).login(email, password);
state = AuthState.authenticated(user);
} catch (e) {
state = AuthState.error(e.toString());
}
}
void logout() {
ref.read(authRepositoryProvider).logout();
state = const AuthState.unauthenticated();
}
}
// Async provider with auto-dispose and caching
@riverpod
Future<List<Product>> products(Ref ref, {String? category}) async {
final repo = ref.read(productRepositoryProvider);
// Auto-cancels when provider is disposed
final link = ref.keepAlive();
// Cache for 5 minutes
final timer = Timer(const Duration(minutes: 5), link.close);
ref.onDispose(timer.cancel);
return repo.fetchProducts(category: category);
}
// Consuming in widgets
class ProductListScreen extends ConsumerWidget {
const ProductListScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final productsAsync = ref.watch(productsProvider());
return productsAsync.when(
data: (products) => ListView.builder(
itemCount: products.length,
itemBuilder: (_, i) => ProductTile(product: products[i]),
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => ErrorWidget(message: err.toString()),
);
}
}
// Impeller is the DEFAULT renderer in Flutter 3.27
// No shader compilation jank — all shaders pre-compiled
// Performance best practices
class OptimizedList extends StatelessWidget {
const OptimizedList({super.key, required this.items});
final List<Item> items;
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
// Provide exact item extent for better scroll performance
itemExtent: 72.0,
itemBuilder: (context, index) => _ItemTile(item: items[index]),
);
}
}
// RepaintBoundary — isolate expensive repaints
class ExpensiveChart extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RepaintBoundary(
child: CustomPaint(painter: ChartPainter(data: data)),
);
}
}
// const constructors — prevent unnecessary rebuilds
class AppTheme {
static const primaryButton = ButtonStyle(
backgroundColor: WidgetStatePropertyAll(Colors.blue),
foregroundColor: WidgetStatePropertyAll(Colors.white),
padding: WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: 24, vertical: 12)),
);
}
// Avoid rebuilding entire tree — push state down
// BAD: setState high in tree
// GOOD: Riverpod select() for granular rebuilds
final userName = ref.watch(userProvider.select((u) => u.name));
// Method channel — call native code
import 'package:flutter/services.dart';
class NativeBridge {
static const _channel = MethodChannel('com.example.app/native');
static Future<String> getBatteryLevel() async {
try {
final result = await _channel.invokeMethod<int>('getBatteryLevel');
return '$result%';
} on PlatformException catch (e) {
return 'Failed: ${e.message}';
}
}
// Event channel — stream from native
static const _eventChannel = EventChannel('com.example.app/sensors');
static Stream<double> get accelerometerStream =>
_eventChannel.receiveBroadcastStream().map((event) => event as double);
}
// Deep linking with GoRouter
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(path: '/', builder: (_, __) => const HomeScreen()),
GoRoute(
path: '/product/:id',
builder: (_, state) => ProductScreen(id: state.pathParameters['id']!),
),
ShellRoute(
builder: (_, __, child) => ScaffoldWithNav(child: child),
routes: [
GoRoute(path: '/home', builder: (_, __) => const HomeTab()),
GoRoute(path: '/search', builder: (_, __) => const SearchTab()),
GoRoute(path: '/profile', builder: (_, __) => const ProfileTab()),
],
),
],
redirect: (context, state) {
final isLoggedIn = /* check auth */;
if (!isLoggedIn && !state.matchedLocation.startsWith('/auth')) {
return '/auth/login';
}
return null;
},
);
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mocktail/mocktail.dart';
// Unit test
test('UserId validates non-empty', () {
expect(() => UserId(''), throwsA(isA<AssertionError>()));
expect(UserId('abc').value, 'abc');
});
// Widget test with Riverpod overrides
testWidgets('shows products when loaded', (tester) async {
final mockProducts = [Product(id: '1', name: 'Widget', price: 9.99)];
await tester.pumpWidget(
ProviderScope(
overrides: [
productsProvider().overrideWith((_) async => mockProducts),
],
child: const MaterialApp(home: ProductListScreen()),
),
);
await tester.pumpAndSettle();
expect(find.text('Widget'), findsOneWidget);
expect(find.text('\$9.99'), findsOneWidget);
});
// Integration test
testWidgets('add to cart flow', (tester) async {
await tester.pumpWidget(const MyApp());
await tester.pumpAndSettle();
await tester.tap(find.text('Widget'));
await tester.pumpAndSettle();
await tester.tap(find.byIcon(Icons.add_shopping_cart));
await tester.pumpAndSettle();
expect(find.text('1 item in cart'), findsOneWidget);
});
// Golden test (visual regression)
testWidgets('ProductCard matches golden', (tester) async {
await tester.pumpWidget(
MaterialApp(home: ProductCard(product: mockProduct)),
);
await expectLater(
find.byType(ProductCard),
matchesGoldenFile('goldens/product_card.png'),
);
});
| Anti-Pattern | Problem | Solution |
|---|---|---|
setState high in widget tree | Rebuilds entire subtree | Push state down, use Riverpod select |
No const constructors | Unnecessary widget rebuilds | Add const everywhere possible |
ListView(children: [...]) | Builds all items upfront | ListView.builder for dynamic lists |
| Business logic in widgets | Untestable, tightly coupled | Notifiers/Blocs with repository pattern |
BuildContext in async gaps | Context may be invalid after await | Check mounted or use ref callbacks |
Nested FutureBuilder | Callback hell, no caching | Riverpod AsyncNotifier |
| No error handling on futures | Silent failures | .catchError or try/catch with user feedback |
| Hardcoded colors/sizes | Inconsistent UI, hard to theme | Theme.of(context) + design tokens |
Before considering Flutter work done:
flutter analyze passes with no issuesdart fix --apply appliedflutter test passes — all tests greenconst used on all possible constructorsbuild_runner)BuildContext used across async gapsListView.builder for all dynamic listsflutter run --route /product/123Semantics widgets on custom components