flutter-expert

Expert-level Flutter patterns for Dart, state management, and cross-platform mobile development.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "flutter-expert" with this command: npx skills add nguyenthienthanh/aura-frog/nguyenthienthanh-aura-frog-flutter-expert

Flutter Expert Skill

Expert-level Flutter patterns for Dart, state management, and cross-platform mobile development.

Auto-Detection

This skill activates when:

  • Working with Flutter projects

  • Detected pubspec.yaml with flutter dependency

  • Working with *.dart files

  • Using Bloc, Riverpod, or Provider

  1. Project Structure

Feature-First Organization

lib/ ├── core/ │ ├── constants/ │ ├── errors/ │ ├── extensions/ │ ├── theme/ │ └── utils/ ├── features/ │ └── auth/ │ ├── data/ │ │ ├── datasources/ │ │ ├── models/ │ │ └── repositories/ │ ├── domain/ │ │ ├── entities/ │ │ ├── repositories/ │ │ └── usecases/ │ └── presentation/ │ ├── bloc/ │ ├── pages/ │ └── widgets/ ├── shared/ │ └── widgets/ └── main.dart

  1. Widget Best Practices

Const Constructors

// ✅ GOOD - Const constructor for immutable widgets class UserCard extends StatelessWidget { const UserCard({ super.key, required this.user, this.onTap, });

final User user; final VoidCallback? onTap;

@override Widget build(BuildContext context) { return Card( child: ListTile( title: Text(user.name), subtitle: Text(user.email), onTap: onTap, ), ); } }

// Usage - benefits from const const UserCard(user: User.empty())

Widget Extraction

// ❌ BAD - Inline complex widgets Widget build(BuildContext context) { return Column( children: [ Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(8), ), child: Column( children: [ Text(user.name, style: Theme.of(context).textTheme.titleLarge), Text(user.email), // ... more widgets ], ), ), ], ); }

// ✅ GOOD - Extract to separate widget class _UserHeader extends StatelessWidget { const _UserHeader({required this.user}); final User user;

@override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(8), ), child: Column( children: [ Text(user.name, style: Theme.of(context).textTheme.titleLarge), Text(user.email), ], ), ); } }

  1. State Management (Riverpod)

Providers

// ✅ GOOD - Typed providers @riverpod class AuthNotifier extends _$AuthNotifier { @override AsyncValue<User?> build() => const AsyncValue.data(null);

Future<void> signIn(String email, String password) async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { final user = await ref.read(authRepositoryProvider).signIn(email, password); return user; }); }

Future<void> signOut() async { await ref.read(authRepositoryProvider).signOut(); state = const AsyncValue.data(null); } }

// Usage in widget class LoginPage extends ConsumerWidget { const LoginPage({super.key});

@override Widget build(BuildContext context, WidgetRef ref) { final authState = ref.watch(authNotifierProvider);

return authState.when(
  data: (user) => user != null ? const HomePage() : const LoginForm(),
  loading: () => const Center(child: CircularProgressIndicator()),
  error: (error, _) => ErrorWidget(error: error),
);

} }

Repository Pattern

// ✅ GOOD - Abstract repository abstract class AuthRepository { Future<User> signIn(String email, String password); Future<void> signOut(); Stream<User?> get authStateChanges; }

@riverpod AuthRepository authRepository(AuthRepositoryRef ref) { return FirebaseAuthRepository( auth: ref.watch(firebaseAuthProvider), ); }

  1. State Management (Bloc)

Bloc Pattern

// Events sealed class AuthEvent {}

class AuthSignInRequested extends AuthEvent { AuthSignInRequested({required this.email, required this.password}); final String email; final String password; }

class AuthSignOutRequested extends AuthEvent {}

// States sealed class AuthState {}

class AuthInitial extends AuthState {} class AuthLoading extends AuthState {} class AuthAuthenticated extends AuthState { AuthAuthenticated({required this.user}); final User user; } class AuthError extends AuthState { AuthError({required this.message}); final String message; }

// Bloc class AuthBloc extends Bloc<AuthEvent, AuthState> { AuthBloc({required AuthRepository authRepository}) : _authRepository = authRepository, super(AuthInitial()) { on<AuthSignInRequested>(_onSignIn); on<AuthSignOutRequested>(_onSignOut); }

final AuthRepository _authRepository;

Future<void> _onSignIn( AuthSignInRequested event, Emitter<AuthState> emit, ) async { emit(AuthLoading()); try { final user = await _authRepository.signIn(event.email, event.password); emit(AuthAuthenticated(user: user)); } catch (e) { emit(AuthError(message: e.toString())); } } }

BlocBuilder Usage

// ✅ GOOD - BlocBuilder with buildWhen BlocBuilder<AuthBloc, AuthState>( buildWhen: (previous, current) => previous != current, builder: (context, state) { return switch (state) { AuthInitial() => const LoginForm(), AuthLoading() => const CircularProgressIndicator(), AuthAuthenticated(:final user) => HomePage(user: user), AuthError(:final message) => ErrorWidget(message: message), }; }, )

  1. Navigation (GoRouter)

// ✅ GOOD - Typed routes final router = GoRouter( initialLocation: '/', redirect: (context, state) { final isLoggedIn = authNotifier.value != null; final isLoggingIn = state.matchedLocation == '/login';

if (!isLoggedIn &#x26;&#x26; !isLoggingIn) return '/login';
if (isLoggedIn &#x26;&#x26; isLoggingIn) return '/';
return null;

}, routes: [ GoRoute( path: '/', builder: (context, state) => const HomePage(), routes: [ GoRoute( path: 'user/:id', builder: (context, state) { final id = state.pathParameters['id']!; return UserPage(userId: id); }, ), ], ), GoRoute( path: '/login', builder: (context, state) => const LoginPage(), ), ], );

  1. Forms & Validation

// ✅ GOOD - Form with validation class LoginForm extends StatefulWidget { const LoginForm({super.key});

@override State<LoginForm> createState() => _LoginFormState(); }

class _LoginFormState extends State<LoginForm> { final _formKey = GlobalKey<FormState>(); final _emailController = TextEditingController(); final _passwordController = TextEditingController();

@override void dispose() { _emailController.dispose(); _passwordController.dispose(); super.dispose(); }

String? _validateEmail(String? value) { if (value == null || value.isEmpty) { return 'Email is required'; } if (!RegExp(r'^[\w-.]+@([\w-]+.)+[\w-]{2,4}$').hasMatch(value)) { return 'Invalid email format'; } return null; }

String? _validatePassword(String? value) { if (value == null || value.isEmpty) { return 'Password is required'; } if (value.length < 8) { return 'Password must be at least 8 characters'; } return null; }

void _submit() { if (_formKey.currentState!.validate()) { // Submit form } }

@override Widget build(BuildContext context) { return Form( key: _formKey, child: Column( children: [ TextFormField( controller: _emailController, decoration: const InputDecoration(labelText: 'Email'), validator: _validateEmail, keyboardType: TextInputType.emailAddress, ), TextFormField( controller: _passwordController, decoration: const InputDecoration(labelText: 'Password'), validator: _validatePassword, obscureText: true, ), ElevatedButton( onPressed: _submit, child: const Text('Login'), ), ], ), ); } }

  1. Performance Optimization

ListView Optimization

// ✅ GOOD - ListView.builder for large lists ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return ItemCard(item: items[index]); }, )

// ✅ GOOD - Add keys for correct diffing ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return ItemCard( key: ValueKey(items[index].id), item: items[index], ); }, )

Const Widgets

// ✅ GOOD - Const for static widgets class MyWidget extends StatelessWidget { const MyWidget({super.key});

@override Widget build(BuildContext context) { return const Column( children: [ Icon(Icons.check), SizedBox(height: 8), Text('Success'), ], ); } }

Selective Rebuilds

// ✅ GOOD - Use Consumer for targeted rebuilds class UserPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('User')), body: Column( children: [ // Only rebuilds when user changes Consumer<UserNotifier>( builder: (context, notifier, child) { return Text(notifier.user.name); }, ), // Never rebuilds const StaticWidget(), ], ), ); } }

  1. Error Handling

Result Pattern

// ✅ GOOD - Sealed class for results sealed class Result<T> { const Result(); }

class Success<T> extends Result<T> { const Success(this.data); final T data; }

class Failure<T> extends Result<T> { const Failure(this.error); final AppException error; }

// Usage Future<Result<User>> getUser(String id) async { try { final user = await _api.getUser(id); return Success(user); } on ApiException catch (e) { return Failure(AppException.fromApi(e)); } }

// Pattern matching final result = await getUser('123'); switch (result) { case Success(:final data): print('User: ${data.name}'); case Failure(:final error): print('Error: ${error.message}'); }

  1. Dependency Injection

// ✅ GOOD - GetIt for DI final getIt = GetIt.instance;

void setupDependencies() { // Services getIt.registerLazySingleton<HttpClient>(() => DioHttpClient());

// Repositories getIt.registerLazySingleton<AuthRepository>( () => AuthRepositoryImpl(client: getIt()), );

// Blocs getIt.registerFactory<AuthBloc>( () => AuthBloc(authRepository: getIt()), ); }

  1. Testing

// ✅ GOOD - Widget testing testWidgets('LoginForm validates email', (tester) async { await tester.pumpWidget( const MaterialApp(home: LoginForm()), );

// Tap submit without entering email await tester.tap(find.byType(ElevatedButton)); await tester.pump();

expect(find.text('Email is required'), findsOneWidget); });

// ✅ GOOD - Bloc testing blocTest<AuthBloc, AuthState>( 'emits [AuthLoading, AuthAuthenticated] when sign in succeeds', build: () { when(() => mockAuthRepository.signIn(any(), any())) .thenAnswer((_) async => testUser); return AuthBloc(authRepository: mockAuthRepository); }, act: (bloc) => bloc.add(AuthSignInRequested( email: 'test@example.com', password: 'password', )), expect: () => [ AuthLoading(), AuthAuthenticated(user: testUser), ], );

Quick Reference

checklist[12]{pattern,best_practice}: Widgets,Const constructors + extract complex State,Riverpod or Bloc pattern Forms,GlobalKey + TextEditingController Lists,ListView.builder with ValueKey Navigation,GoRouter typed routes DI,GetIt or Riverpod providers Errors,Sealed Result class Testing,Widget tests + bloc_test Rebuilds,Consumer for targeted updates Dispose,Always dispose controllers Null safety,Pattern matching Performance,const widgets + RepaintBoundary

Version: 1.3.0

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

python-expert

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

code-simplifier

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

code-reviewer

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

dev-expert

No summary provided by upstream source.

Repository SourceNeeds Review