agentsclimarketplace

Flutter state management and widget design

Skill hamzabellouch/agent-skills/Mobile Development/flutter-state-management-and-widget-design

Comprehensive collection of 380+ production-ready Agent Skills (26 domains) conforming to the Agent Skills Standard, featuring native auto-discovery for Antigravity, Gemini CLI, Claude Code, Cursor, and Codex.

Install
npx -y skills add hamzabellouch/agent-skills --skill flutter-state-management-and-widget-design

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • 21 days oldThe repository was created 21 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Production-grade Flutter state management (Riverpod 2.x, BLoC/Cubit), widget tree optimization, immutable state modeling, declarative routing (GoRouter), clean architecture, and custom rendering performance patterns. Use when architecting or building scalable Flutter applications.

SKILL.md

7.7 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it

Flutter State Management & Widget Design

A comprehensive guide for building scalable, high-performance, and maintainable Flutter applications using Riverpod 2.x, BLoC/Cubit, GoRouter, and performance-first widget tree architecture.


1. State Management Architecture: Riverpod 2.x vs BLoC

Standard Decision Matrix

Metric / RequirementRiverpod 2.x (Notifier / AsyncNotifier)BLoC / Cubit
Compile-time SafetyHighest (No ProviderNotFoundException)High (Depends on BuildContext hierarchy)
TestabilityEasy override via ProviderContainerEasy via bloc_test
Dependency InjectionBuilt-in via Provider compositionRequires RepositoryProvider / get_it
Learning CurveModerateModerate-High (Event/State boilerplate)
Use Case RecommendationModern greenfield Flutter appsCorporate/Enterprise apps with strict event logging

2. Riverpod 2.x Production Pattern (AsyncNotifier)

Always use @riverpod code generation or AsyncNotifierProvider for async state to handle AsyncValue (data, loading, error) cleanly.

Immutable State & Controller Implementation

import 'package:flutter_riverpod/flutter_riverpod.dart';

// --- Domain Model ---
class UserProfile {
  final String id;
  final String name;
  final String email;

  const UserProfile({
    required this.id,
    required this.name,
    required this.email,
  });

  UserProfile copyWith({String? name, String? email}) {
    return UserProfile(
      id: id,
      name: name ?? this.name,
      email: email ?? this.email,
    );
  }
}

// --- Repository Interface ---
abstract class UserRepository {
  Future<UserProfile> fetchProfile(String userId);
  Future<void> updateName(String userId, String newName);
}

// --- Repository Provider ---
final userRepositoryProvider = Provider<UserRepository>((ref) {
  throw UnimplementedError('Override in ProviderScope');
});

// --- AsyncNotifier Controller ---
final userProfileControllerProvider =
    AsyncNotifierProvider.family<UserProfileController, UserProfile, String>(
  UserProfileController.new,
);

class UserProfileController extends FamilyAsyncNotifier<UserProfile, String> {
  @override
  Future<UserProfile> build(String arg) async {
    final repository = ref.watch(userRepositoryProvider);
    return repository.fetchProfile(arg);
  }

  Future<void> updateName(String newName) async {
    final repository = ref.read(userRepositoryProvider);
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(() async {
      await repository.updateName(arg, newName);
      final current = state.valueOrNull ?? await repository.fetchProfile(arg);
      return current.copyWith(name: newName);
    });
  }
}

3. Widget Tree Optimization Guidelines

Rule 1: Use const Constructors Everywhere

const widgets avoid unnecessary element updates and skip re-building when parent widgets re-render.

Rule 2: Rebuild Granularly with select

Do not watch an entire state object if you only need a single field.

// ❌ BAD: Rebuilds every time ANY field in UserProfile changes
class UserHeader extends ConsumerWidget {
  final String userId;
  const UserHeader({super.key, required this.userId});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final asyncProfile = ref.watch(userProfileControllerProvider(userId));
    return Text(asyncProfile.valueOrNull?.name ?? '');
  }
}

// ✅ GOOD: Rebuilds ONLY when 'name' specifically changes
class UserHeaderOptimized extends ConsumerWidget {
  final String userId;
  const UserHeaderOptimized({super.key, required this.userId});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final name = ref.watch(
      userProfileControllerProvider(userId).select((asyncVal) => asyncVal.valueOrNull?.name),
    );
    return Text(name ?? '');
  }
}

Rule 3: Isolate Heavy Animations with RepaintBoundary

Wrap isolated, high-frequency animated subtrees in RepaintBoundary to prevent rasterizing the whole screen on every tick.

Widget build(BuildContext context) {
  return Scaffold(
    body: Stack(
      children: [
        const StaticBackgroundView(),
        RepaintBoundary(
          child: CustomParticleAnimation(),
        ),
      ],
    ),
  );
}

4. Declarative Routing with GoRouter

Implement type-safe, declarative routes with authentication guards.

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final authStateProvider = StateProvider<bool>((ref) => false);

final routerProvider = Provider<GoRouter>((ref) {
  final isAuthenticated = ref.watch(authStateProvider);

  return GoRouter(
    initialLocation: '/home',
    redirect: (context, state) {
      final loggingIn = state.matchedLocation == '/login';
      if (!isAuthenticated && !loggingIn) return '/login';
      if (isAuthenticated && loggingIn) return '/home';
      return null;
    },
    routes: [
      GoRoute(
        path: '/login',
        builder: (context, state) => const LoginScreen(),
      ),
      GoRoute(
        path: '/home',
        builder: (context, state) => const HomeScreen(),
        routes: [
          GoRoute(
            path: 'details/:id',
            builder: (context, state) {
              final id = state.pathParameters['id']!;
              return DetailsScreen(id: id);
            },
          ),
        ],
      ),
    ],
  );
});

5. Critical Anti-Patterns & Pitfalls

❌ Anti-Pattern 1: Using BuildContext Across Async Gaps

Bad:

void onSubmit(BuildContext context) async {
  await fetchData();
  Navigator.of(context).pop(); // Dangerous! Context may no longer be mounted.
}

Good:

void onSubmit(BuildContext context) async {
  await fetchData();
  if (!context.mounted) return;
  Navigator.of(context).pop();
}

❌ Anti-Pattern 2: Instantiating Controllers Inside build()

Bad:

Widget build(BuildContext context) {
  final controller = AnimationController(vsync: this, duration: ...); // Memory leak! Re-created on rebuild!
  return ...
}

Good: Initialize long-lived controllers in initState() / disposal in dispose(), or use flutter_hooks (useAnimationController).

❌ Anti-Pattern 3: Over-nesting Layout Widgets

Avoid deep nesting of Container, Padding, and SizedBox. Prefer explicit single-pass layout widgets (Padding, Gap, Flex, CustomScrollView with SliverList).


6. Unit & Widget Testing Patterns

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mockito/mockito.dart';

void main() {
  testWidgets('UserHeader displays name from controller', (tester) async {
    final mockRepository = MockUserRepository();
    when(mockRepository.fetchProfile('123')).thenAnswer(
      (_) async => const UserProfile(id: '123', name: 'Alice', email: '[email protected]'),
    );

    await tester.pumpWidget(
      ProviderScope(
        overrides: [
          userRepositoryProvider.overrideWithValue(mockRepository),
        ],
        child: const MaterialApp(
          home: UserHeaderOptimized(userId: '123'),
        ),
      ),
    );

    // Initial state (loading)
    expect(find.byType(Text), findsOneWidget);

    // Settle async notifier
    await tester.pumpAndSettle();

    expect(find.text('Alice'), findsOneWidget);
  });
}

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,984. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.