moai-lang-flutter
À propos
Cette compétence Claude se spécialise dans le développement Flutter 3.24+ et Dart 3.5+, en se concentrant sur la gestion d'état moderne avec Riverpod et la navigation déclarative utilisant go_router. Elle est conçue pour la création d'applications multiplateformes mobiles, de bureau et web. Utilisez cette compétence pour obtenir des conseils sur les mises en page adaptatives, les dernières fonctionnalités du langage Dart et l'intégration de fonctionnalités spécifiques aux plateformes.
Installation rapide
Claude Code
Recommandénpx skills add modu-ai/moai-adk/plugin add https://github.com/modu-ai/moai-adkgit clone https://github.com/modu-ai/moai-adk.git ~/.claude/skills/moai-lang-flutterCopiez et collez cette commande dans Claude Code pour installer cette compétence
Documentation
Quick Reference (30 seconds)
Flutter/Dart Development Expert - Dart 3.5+, Flutter 3.24+ with modern patterns.
Auto-Triggers: Flutter projects (.dart files, pubspec.yaml), cross-platform apps, widget development
Core Capabilities:
- Dart 3.5: Patterns, records, sealed classes, extension types
- Flutter 3.24: Widget tree, Material 3, adaptive layouts
- Riverpod: State management with code generation
- go_router: Declarative navigation and deep linking
- Platform Channels: Native iOS/Android integration
- Testing: flutter_test, widget_test, integration_test
Implementation Guide (5 minutes)
Dart 3.5 Language Features
Pattern Matching with Sealed Classes:
sealed class Result<T> {
const Result();
}
class Success<T> extends Result<T> {
final T data;
const Success(this.data);
}
class Failure<T> extends Result<T> {
final String error;
const Failure(this.error);
}
// Exhaustive switch expression
String handleResult(Result<User> result) => switch (result) {
Success(:final data) => 'User: ${data.name}',
Failure(:final error) => 'Error: $error',
};
// Guard clauses in patterns
String describeUser(User user) => switch (user) {
User(age: var a) when a < 18 => 'Minor',
User(age: var a) when a >= 65 => 'Senior',
User(name: var n, age: var a) => '$n, age $a',
};
Records and Destructuring:
typedef UserRecord = ({String name, int age, String email});
// Multiple return values
(String name, int age) parseUser(Map<String, dynamic> json) {
return (json['name'] as String, json['age'] as int);
}
// Destructuring
void processUser(Map<String, dynamic> json) {
final (name, age) = parseUser(json);
print('$name is $age years old');
}
// Record patterns in collections
void processUsers(List<UserRecord> users) {
for (final (:name, :age, :email) in users) {
print('$name ($age): $email');
}
}
Extension Types:
extension type UserId(String value) {
factory UserId.generate() => UserId(Uuid().v4());
bool get isValid => value.isNotEmpty;
}
extension type Email(String value) {
bool get isValid => value.contains('@') && value.contains('.');
String get domain => value.split('@').last;
}
Riverpod State Management
Provider Definitions:
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'providers.g.dart';
@riverpod
UserRepository userRepository(Ref ref) {
return UserRepository(ref.read(dioProvider));
}
@riverpod
Future<User> user(Ref ref, String userId) async {
return ref.watch(userRepositoryProvider).getUser(userId);
}
@riverpod
class UserNotifier extends _$UserNotifier {
@override
FutureOr<User?> build() => null;
Future<void> loadUser(String id) async {
state = const AsyncLoading();
state = await AsyncValue.guard(
() => ref.read(userRepositoryProvider).getUser(id),
);
}
Future<void> updateUser(User user) async {
state = const AsyncLoading();
state = await AsyncValue.guard(
() => ref.read(userRepositoryProvider).updateUser(user),
);
}
}
@riverpod
Stream<List<Message>> messages(Ref ref, String chatId) {
return ref.watch(chatRepositoryProvider).watchMessages(chatId);
}
Widget Integration:
class UserScreen extends ConsumerWidget {
final String userId;
const UserScreen({required this.userId, super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider(userId));
return Scaffold(
appBar: AppBar(title: const Text('User Profile')),
body: userAsync.when(
data: (user) => UserProfile(user: user),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Error: $error'),
ElevatedButton(
onPressed: () => ref.invalidate(userProvider(userId)),
child: const Text('Retry'),
),
],
),
),
),
);
}
}
StatefulWidget with Riverpod:
class EditUserScreen extends ConsumerStatefulWidget {
const EditUserScreen({super.key});
@override
ConsumerState<EditUserScreen> createState() => _EditUserScreenState();
}
class _EditUserScreenState extends ConsumerState<EditUserScreen> {
final _formKey = GlobalKey<FormState>();
late TextEditingController _nameController;
@override
void initState() {
super.initState();
_nameController = TextEditingController();
}
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
ref.listen(userNotifierProvider, (prev, next) {
next.whenOrNull(
error: (e, _) => ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
),
);
});
final isLoading = ref.watch(userNotifierProvider).isLoading;
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _nameController,
validator: (v) => v?.isEmpty ?? true ? 'Required' : null,
),
ElevatedButton(
onPressed: isLoading ? null : () async {
if (_formKey.currentState!.validate()) {
await ref.read(userNotifierProvider.notifier)
.updateUser(User(name: _nameController.text));
}
},
child: isLoading
? const CircularProgressIndicator()
: const Text('Save'),
),
],
),
);
}
}
go_router Navigation
Router Configuration:
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
name: 'home',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'user/:id',
name: 'user-detail',
builder: (context, state) => UserDetailScreen(
userId: state.pathParameters['id']!,
),
),
],
),
ShellRoute(
builder: (context, state, child) => MainShell(child: child),
routes: [
GoRoute(
path: '/feed',
pageBuilder: (_, __) => const NoTransitionPage(child: FeedScreen()),
),
GoRoute(
path: '/search',
pageBuilder: (_, __) => const NoTransitionPage(child: SearchScreen()),
),
GoRoute(
path: '/profile',
pageBuilder: (_, __) => const NoTransitionPage(child: ProfileScreen()),
),
],
),
],
redirect: (context, state) {
final isLoggedIn = authNotifier.isLoggedIn;
final isLoggingIn = state.matchedLocation == '/login';
if (!isLoggedIn && !isLoggingIn) return '/login';
if (isLoggedIn && isLoggingIn) return '/';
return null;
},
errorBuilder: (context, state) => ErrorScreen(error: state.error),
);
// Navigation methods
void navigateToUser(BuildContext context, String userId) {
context.go('/user/$userId');
}
void goBack(BuildContext context) {
if (context.canPop()) context.pop();
else context.go('/');
}
Platform Channels
Dart Implementation:
class NativeBridge {
static const _channel = MethodChannel('com.example.app/native');
static const _eventChannel = EventChannel('com.example.app/events');
Future<String> getPlatformVersion() async {
try {
final version = await _channel.invokeMethod<String>('getPlatformVersion');
return version ?? 'Unknown';
} on PlatformException catch (e) {
throw NativeBridgeException('Failed: ${e.message}');
}
}
Future<void> shareContent({required String text, String? title}) async {
await _channel.invokeMethod('share', {
'text': text,
if (title != null) 'title': title,
});
}
Stream<BatteryState> watchBatteryState() {
return _eventChannel.receiveBroadcastStream().map((event) {
final data = event as Map<dynamic, dynamic>;
return BatteryState(
level: data['level'] as int,
isCharging: data['isCharging'] as bool,
);
});
}
void setupMethodCallHandler() {
_channel.setMethodCallHandler((call) async {
switch (call.method) {
case 'onNativeEvent':
// Handle native event
return true;
default:
throw MissingPluginException('Not implemented: ${call.method}');
}
});
}
}
class BatteryState {
final int level;
final bool isCharging;
const BatteryState({required this.level, required this.isCharging});
}
Widget Patterns
Adaptive Layouts:
class AdaptiveScaffold extends StatelessWidget {
final Widget child;
final List<NavigationDestination> destinations;
final int selectedIndex;
final ValueChanged<int> onDestinationSelected;
const AdaptiveScaffold({
required this.child,
required this.destinations,
required this.selectedIndex,
required this.onDestinationSelected,
super.key,
});
@override
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
if (width < 600) {
return Scaffold(
body: child,
bottomNavigationBar: NavigationBar(
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
destinations: destinations,
),
);
}
if (width < 840) {
return Scaffold(
body: Row(children: [
NavigationRail(
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
destinations: destinations.map((d) => NavigationRailDestination(
icon: d.icon, selectedIcon: d.selectedIcon, label: Text(d.label),
)).toList(),
),
const VerticalDivider(thickness: 1, width: 1),
Expanded(child: child),
]),
);
}
return Scaffold(
body: Row(children: [
NavigationDrawer(
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
children: destinations.map((d) => NavigationDrawerDestination(
icon: d.icon, selectedIcon: d.selectedIcon ?? d.icon, label: Text(d.label),
)).toList(),
),
const VerticalDivider(thickness: 1, width: 1),
Expanded(child: child),
]),
);
}
}
Testing
Widget Test Example:
void main() {
testWidgets('UserScreen displays data', (tester) async {
final container = ProviderContainer(overrides: [
userRepositoryProvider.overrideWithValue(MockUserRepository()),
]);
await tester.pumpWidget(
UncontrolledProviderScope(
container: container,
child: const MaterialApp(home: UserScreen(userId: '1')),
),
);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
await tester.pumpAndSettle();
expect(find.text('Test User'), findsOneWidget);
});
}
For comprehensive testing patterns, see examples.md.
Advanced Patterns
For comprehensive coverage including:
- Clean Architecture with Riverpod
- Isolates for compute-heavy operations
- Custom render objects and painting
- FFI and platform-specific plugins
- Performance optimization and profiling
See: reference.md and examples.md
Context7 Library Mappings
Flutter/Dart Core:
/flutter/flutter- Flutter framework/dart-lang/sdk- Dart SDK
State Management:
/rrousselGit/riverpod- Riverpod state management/felangel/bloc- BLoC pattern
Navigation and Storage:
/flutter/packages- go_router and official packages/cfug/dio- HTTP client/isar/isar- NoSQL database
Works Well With
moai-lang-swift- iOS native integration for platform channelsmoai-lang-kotlin- Android native integration for platform channelsmoai-domain-backend- API integration and backend communicationmoai-quality-security- Mobile security best practicesmoai-essentials-debug- Flutter debugging and DevTools
Version: 1.0.0 Last Updated: 2025-12-07 Status: Production Ready
Dépôt GitHub
Compétences associées
mobile-testing
AutreThis Claude Skill provides comprehensive mobile testing for iOS and Android applications, covering gestures, sensors, permissions, and device fragmentation. Use it when testing native, hybrid, or mobile web apps to ensure quality across 1000+ device variants. It helps define device coverage matrices and test key platform differences.
moai-domain-mobile-app
TestsThis Claude Skill provides enterprise mobile development expertise for React Native 0.76+, Flutter 3.24+, and Capacitor 6.x cross-platform frameworks. It focuses on implementing robust patterns, comprehensive testing, and CI/CD automation for production-ready mobile applications. Use this skill for guidance on mobile architecture, performance optimization, and deployment strategies.
moai-domain-mobile-app
TestsThis Claude Skill provides enterprise mobile development expertise for React Native 0.76+, Flutter 3.24+, and Capacitor 6.x frameworks. It focuses on cross-platform patterns, testing strategies, and CI/CD automation for production-ready applications. Use this skill for guidance on modern mobile development workflows and deployment best practices.
rust-desktop-applications
DéveloppementThis skill helps developers build cross-platform desktop applications using Rust, primarily through the Tauri framework or native GUI alternatives. It's ideal for creating performant apps requiring system integration, small bundle sizes, and high performance. The guidance covers project setup, IPC, state management, and cross-platform testing and deployment.
