Loading skill
Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Flutter development with Riverpod state management, Freezed, go_router, and mocktail testing
.claude/skills/alinaqi-flutter/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 106% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 146% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 109% | 0% |
| case-06 | ✓→✓ | = Same ✓ | 179% | 0% |
| case-08 | ✓→✓ | = Same ✓ | 167% | 0% |
project/
├── lib/
│ ├── core/ # Core utilities
│ │ ├── constants/ # App constants
│ │ ├── extensions/ # Dart extensions
│ │ ├── router/ # go_router configuration
│ │ │ └── app_router.dart
│ │ └── theme/ # App theme
│ │ └── app_theme.dart
│ ├── data/ # Data layer
│ │ ├── models/ # Freezed data models
│ │ ├── repositories/ # Repository implementations
│ │ └── services/ # API services
│ ├── domain/ # Domain layer
│ │ ├── entities/ # Business entities
│ │ └── repositories/ # Repository interfaces
│ ├── presentation/ # UI layer
│ │ ├── common/ # Shared widgets
│ │ ├── features/ # Feature modules
│ │ │ └── feature_name/
│ │ │ ├── providers/ # Riverpod providers
│ │ │ ├── widgets/ # Feature-specific widgets
│ │ │ └── feature_screen.dart
│ │ └── providers/ # Global providers
│ ├── main.dart
│ └── app.dart
├── test/
│ ├── unit/ # Unit tests
│ ├── widget/ # Widget tests
│ └── integration/ # Integration tests
├── pubspec.yaml
├── analysis_options.yaml
└── CLAUDE.mddart// Simple value provider final appNameProvider = Provider<String>((ref) => 'My App'); // StateProvider for simple mutable state final counterProvider = StateProvider<int>((ref) => 0); // NotifierProvider for complex state logic final userProvider = NotifierProvider<UserNotifier, User?>(() => UserNotifier()); // AsyncNotifierProvider for async operations final usersProvider = AsyncNotifierProvider<UsersNotifier, List<User>>( () => UsersNotifier(), ); // FutureProvider for simple async data final configProvider = FutureProvider<Config>((ref) async { return await ref.watch(configServiceProvider).loadConfig(); }); // StreamProvider for real-time data final messagesProvider = StreamProvider<List<Message>>((ref) { return ref.watch(messageServiceProvider).watchMessages(); }); // Family providers for parameterized data final userByIdProvider = FutureProvider.family<User, String>((ref, userId) async { return await ref.watch(userRepositoryProvider).getUser(userId); });
dart@riverpod class Users extends _$Users { @override Future<List<User>> build() async { return await _fetchUsers(); } Future<List<User>> _fetchUsers() async { final repository = ref.read(userRepositoryProvider); return await repository.getUsers(); } Future<void> refresh() async { state = const AsyncLoading(); state = await AsyncValue.guard(() => _fetchUsers()); } Future<void> addUser(User user) async { final repository = ref.read(userRepositoryProvider); await repository.addUser(user); ref.invalidateSelf(); } }
dartclass UsersScreen extends ConsumerWidget { const UsersScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final usersAsync = ref.watch(usersProvider); return usersAsync.when( data: (users) => UsersList(users: users), loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) => ErrorDisplay( error: error, onRetry: () => ref.invalidate(usersProvider), ), ); } } // Pattern matching alternative Widget build(BuildContext context, WidgetRef ref) { final usersAsync = ref.watch(usersProvider); return switch (usersAsync) { AsyncData(:final value) => UsersList(users: value), AsyncLoading() => const LoadingIndicator(), AsyncError(:final error) => ErrorDisplay(error: error), }; }
dart// watch - rebuilds when provider changes final users = ref.watch(usersProvider); // read - one-time read, no rebuild void onButtonPressed() { ref.read(counterProvider.notifier).state++; } // listen - react to changes without rebuild ref.listen(authProvider, (previous, next) { if (next == null) { context.go('/login'); } }); // invalidate - force refresh ref.invalidate(usersProvider); // keepAlive - prevent auto-dispose final link = ref.keepAlive(); // Later: link.close() to allow disposal
dartimport 'package:freezed_annotation/freezed_annotation.dart'; part 'user.freezed.dart'; part 'user.g.dart'; @freezed class User with _$User { const factory User({ required String id, required String name, required String email, @Default(false) bool isActive, DateTime? createdAt, }) = _User; factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json); } // Union types for states @freezed sealed class AuthState with _$AuthState { const factory AuthState.initial() = _Initial; const factory AuthState.loading() = _Loading; const factory AuthState.authenticated(User user) = _Authenticated; const factory AuthState.unauthenticated() = _Unauthenticated; const factory AuthState.error(String message) = _Error; }
dartWidget build(BuildContext context, WidgetRef ref) { final authState = ref.watch(authProvider); return authState.when( initial: () => const SplashScreen(), loading: () => const LoadingScreen(), authenticated: (user) => HomeScreen(user: user), unauthenticated: () => const LoginScreen(), error: (message) => ErrorScreen(message: message), ); }
dartfinal routerProvider = Provider<GoRouter>((ref) { final authState = ref.watch(authProvider); return GoRouter( initialLocation: '/', refreshListenable: authState, redirect: (context, state) { final isLoggedIn = authState.valueOrNull != null; final isLoggingIn = state.matchedLocation == '/login'; if (!isLoggedIn && !isLoggingIn) return '/login'; if (isLoggedIn && isLoggingIn) return '/'; return null; }, routes: [ GoRoute( path: '/', builder: (context, state) => const HomeScreen(), routes: [ GoRoute( path: 'user/:id', builder: (context, state) => UserScreen( userId: state.pathParameters['id']!, ), ), ], ), GoRoute( path: '/login', builder: (context, state) => const LoginScreen(), ), ], errorBuilder: (context, state) => ErrorScreen(error: state.error), ); });
dart// Navigate to route context.go('/user/123'); // Push onto stack context.push('/user/123'); // Pop current route context.pop(); // Replace current route context.pushReplacement('/home'); // Named routes context.goNamed('user', pathParameters: {'id': '123'});
dart// Stateless with Riverpod class UserCard extends ConsumerWidget { const UserCard({super.key, required this.userId}); final String userId; @override Widget build(BuildContext context, WidgetRef ref) { final user = ref.watch(userByIdProvider(userId)); return user.when( data: (user) => Card(child: Text(user.name)), loading: () => const CardSkeleton(), error: (e, _) => ErrorCard(error: e), ); } } // Stateful with Riverpod class SearchScreen extends ConsumerStatefulWidget { const SearchScreen({super.key}); @override ConsumerState<SearchScreen> createState() => _SearchScreenState(); } class _SearchScreenState extends ConsumerState<SearchScreen> { final _controller = TextEditingController(); @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final results = ref.watch(searchProvider(_controller.text)); return Column( children: [ TextField( controller: _controller, onChanged: (_) => setState(() {}), ), Expanded(child: SearchResults(results: results)), ], ); } }
dartclass AnimatedCounter extends HookConsumerWidget { const AnimatedCounter({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final controller = useAnimationController(duration: const Duration(milliseconds: 300)); final count = ref.watch(counterProvider); useEffect(() { controller.forward(from: 0); return null; }, [count]); return ScaleTransition( scale: controller, child: Text('$count'), ); } }
dartimport 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:riverpod/riverpod.dart'; class MockUserRepository extends Mock implements UserRepository {} void main() { late MockUserRepository mockRepository; late ProviderContainer container; setUp(() { mockRepository = MockUserRepository(); container = ProviderContainer( overrides: [ userRepositoryProvider.overrideWithValue(mockRepository), ], ); }); tearDown(() { container.dispose(); }); test('usersProvider returns list of users', () async { final users = [User(id: '1', name: 'John', email: 'john@example.com')]; when(() => mockRepository.getUsers()).thenAnswer((_) async => users); final result = await container.read(usersProvider.future); expect(result, equals(users)); verify(() => mockRepository.getUsers()).called(1); }); }
dartvoid main() { testWidgets('UserCard displays user name', (tester) async { final user = User(id: '1', name: 'John', email: 'john@example.com'); await tester.pumpWidget( ProviderScope( overrides: [ userByIdProvider('1').overrideWith((_) => AsyncData(user)), ], child: const MaterialApp(home: UserCard(userId: '1')), ), ); expect(find.text('John'), findsOneWidget); }); testWidgets('UserCard shows loading indicator', (tester) async { await tester.pumpWidget( ProviderScope( overrides: [ userByIdProvider('1').overrideWith((_) => const AsyncLoading()), ], child: const MaterialApp(home: UserCard(userId: '1')), ), ); expect(find.byType(CircularProgressIndicator), findsOneWidget); }); }
yamlname: my_app description: A Flutter application publish_to: 'none' version: 1.0.0+1 environment: sdk: '>=3.2.0 <4.0.0' dependencies: flutter: sdk: flutter # State management flutter_riverpod: ^2.4.9 riverpod_annotation: ^2.3.3 # Data models freezed_annotation: ^2.4.1 json_annotation: ^4.8.1 # Navigation go_router: ^13.0.0 # Networking dio: ^5.4.0 # Storage shared_preferences: ^2.2.2 # Utils intl: ^0.19.0 dev_dependencies: flutter_test: sdk: flutter # Code generation build_runner: ^2.4.8 freezed: ^2.4.6 json_serializable: ^6.7.1 riverpod_generator: ^2.3.9 # Testing mocktail: ^1.0.2 # Linting flutter_lints: ^3.0.1
yamlname: Flutter CI on: push: branches: [main] pull_request: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: flutter-version: '3.16.0' channel: 'stable' cache: true - name: Install dependencies run: flutter pub get - name: Generate code run: dart run build_runner build --delete-conflicting-outputs - name: Analyze run: flutter analyze --fatal-infos - name: Run tests run: flutter test --coverage - name: Build APK run: flutter build apk --release
yamlinclude: package:flutter_lints/flutter.yaml analyzer: exclude: - "**/*.g.dart" - "**/*.freezed.dart" errors: invalid_annotation_target: ignore language: strict-casts: true strict-inference: true strict-raw-types: true linter: rules: - always_declare_return_types - avoid_dynamic_calls - avoid_print - avoid_type_to_string - cancel_subscriptions - close_sinks - prefer_const_constructors - prefer_const_declarations - prefer_final_locals - require_trailing_commas - unawaited_futures - use_super_parameters
.autoDispose to prevent memory leaksref.read() in onPressed/callbacks, not ref.watch()when().family for parameterized providersOther measured skills in the registry, with their headline benchmark lift.