MCP HubMCP Hub
스킬 목록으로 돌아가기

moai-lang-flutter

modu-ai
업데이트됨 Today
160 조회
424
78
424
GitHub에서 보기
기타flutterdartriverpodcross-platformmobiledesktop

정보

이 Claude Skill은 Flutter 3.24+와 Dart 3.5+ 개발을 전문으로 하며, Riverpod를 활용한 현대적 상태 관리와 go_router를 이용한 선언형 네비게이션에 중점을 둡니다. 크로스 플랫폼 모바일, 데스크톱, 웹 애플리케이션 구축을 위해 설계되었습니다. 적응형 레이아웃, Dart의 최신 언어 기능, 플랫폼별 기능 통합에 대한 지침을 얻을 때 이 기술을 활용하세요.

빠른 설치

Claude Code

추천
기본
npx skills add modu-ai/moai-adk
플러그인 명령대체
/plugin add https://github.com/modu-ai/moai-adk
Git 클론대체
git clone https://github.com/modu-ai/moai-adk.git ~/.claude/skills/moai-lang-flutter

Claude Code에서 이 명령을 복사하여 붙여넣어 스킬을 설치하세요

문서

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 channels
  • moai-lang-kotlin - Android native integration for platform channels
  • moai-domain-backend - API integration and backend communication
  • moai-quality-security - Mobile security best practices
  • moai-essentials-debug - Flutter debugging and DevTools

Version: 1.0.0 Last Updated: 2025-12-07 Status: Production Ready

GitHub 저장소

modu-ai/moai-adk
경로: src/moai_adk/templates/.claude/skills/moai-lang-flutter
agentic-aiagentic-codingagentic-workflowclaudeclaudecodevibe-coding

연관 스킬

mobile-testing

기타

This 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

테스팅

This 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.

스킬 보기

rust-desktop-applications

개발

This 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.

스킬 보기

moai-domain-mobile-app

테스팅

This 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.

스킬 보기