alinaqi/claude-bootstrap/flutter
Trust tier 2
- community
- By alinaqi
- Coding
- alinaqi/claude-bootstrap
- skills/flutter/SKILL.md
- Updated 17 May 2026
- Version 1.0.12 versions recorded
Source check pending
Checking that the SKILL.md file is still in the repository.
Flutter development with Riverpod state management, Freezed, go_router, and mocktail testing
Install
npx vskill@latest install alinaqi/claude-bootstrap/flutterSKILL.md
Flutter Skill
Project Structure
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.md
Riverpod State Management
Provider Types
dart
// Simple value providerfinal appNameProvider = Provider<String>((ref) => 'My App');// StateProvider for simple mutable statefinal counterProvider = StateProvider<int>((ref) => 0);// NotifierProvider for complex state logicfinal userProvider = NotifierProvider<UserNotifier, User?>(() => UserNotifier());// AsyncNotifierProvider for async operationsfinal usersProvider = AsyncNotifierProvider<UsersNotifier, List<User>>(() => UsersNotifier(),);// FutureProvider for simple async datafinal configProvider = FutureProvider<Config>((ref) async {return await ref.watch(configServiceProvider).loadConfig();});// StreamProvider for real-time datafinal messagesProvider = StreamProvider<List<Message>>((ref) {return ref.watch(messageServiceProvider).watchMessages();});// Family providers for parameterized datafinal userByIdProvider = FutureProvider.family<User, String>((ref, userId) async {return await ref.watch(userRepositoryProvider).getUser(userId);});
Notifier Pattern
dart
@riverpodclass Users extends _$Users {@overrideFuture<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();}}
AsyncValue Handling
dart
class UsersScreen extends ConsumerWidget {const UsersScreen({super.key});@overrideWidget 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 alternativeWidget 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),};}
ref Methods
dart
// watch - rebuilds when provider changesfinal users = ref.watch(usersProvider);// read - one-time read, no rebuildvoid onButtonPressed() {ref.read(counterProvider.notifier).state++;}// listen - react to changes without rebuildref.listen(authProvider, (previous, next) {if (next == null) {context.go('/login');}});// invalidate - force refreshref.invalidate(usersProvider);// keepAlive - prevent auto-disposefinal link = ref.keepAlive();// Later: link.close() to allow disposal
Freezed Data Models
Model Definition
dart
import 'package:freezed_annotation/freezed_annotation.dart';part 'user.freezed.dart';part 'user.g.dart';@freezedclass 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@freezedsealed 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;}
Using Freezed Unions
dart
Widget 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),);}
go_router Navigation
Router Configuration
dart
final 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),);});
Navigation
dart
// Navigate to routecontext.go('/user/123');// Push onto stackcontext.push('/user/123');// Pop current routecontext.pop();// Replace current routecontext.pushReplacement('/home');// Named routescontext.goNamed('user', pathParameters: {'id': '123'});
Widget Patterns
ConsumerWidget vs ConsumerStatefulWidget
dart
// Stateless with Riverpodclass UserCard extends ConsumerWidget {const UserCard({super.key, required this.userId});final String userId;@overrideWidget 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 Riverpodclass SearchScreen extends ConsumerStatefulWidget {const SearchScreen({super.key});@overrideConsumerState<SearchScreen> createState() => _SearchScreenState();}class _SearchScreenState extends ConsumerState<SearchScreen> {final _controller = TextEditingController();@overridevoid dispose() {_controller.dispose();super.dispose();}@overrideWidget build(BuildContext context) {final results = ref.watch(searchProvider(_controller.text));return Column(children: [TextField(controller: _controller,onChanged: (_) => setState(() {}),),Expanded(child: SearchResults(results: results)),],);}}
HookConsumerWidget (with flutter_hooks)
dart
class AnimatedCounter extends HookConsumerWidget {const AnimatedCounter({super.key});@overrideWidget 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'),);}}
Testing with Mocktail
Unit Tests
dart
import '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);});}
Widget Tests
dart
void 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);});}
pubspec.yaml
yaml
name: my_appdescription: A Flutter applicationpublish_to: 'none'version: 1.0.0+1environment:sdk: '>=3.2.0 <4.0.0'dependencies:flutter:sdk: flutter# State managementflutter_riverpod: ^2.4.9riverpod_annotation: ^2.3.3# Data modelsfreezed_annotation: ^2.4.1json_annotation: ^4.8.1# Navigationgo_router: ^13.0.0# Networkingdio: ^5.4.0# Storageshared_preferences: ^2.2.2# Utilsintl: ^0.19.0dev_dependencies:flutter_test:sdk: flutter# Code generationbuild_runner: ^2.4.8freezed: ^2.4.6json_serializable: ^6.7.1riverpod_generator: ^2.3.9# Testingmocktail: ^1.0.2# Lintingflutter_lints: ^3.0.1
GitHub Actions
yaml
name: Flutter CIon:push:branches: [main]pull_request:branches: [main]jobs:build:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- uses: subosito/flutter-action@v2with:flutter-version: '3.16.0'channel: 'stable'cache: true- name: Install dependenciesrun: flutter pub get
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/flutter/SKILL.md
Popularity
- Repository stars
- 631
- Repository forks
- 54
- 7-day trend score
- 32
Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 17 Mar 2026, 7 months ago.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/alinaqi/claude-bootstrap/flutter)Something wrong with this skill or its page? Tell us.