本文へ移動
cccskills
無料GitHub で公開

managing-flutter-provider-state

Provider (v6.x) state management for Flutter using ChangeNotifier and InheritedWidget with MVVM architecture pattern. Use this skill when implementing Provider-based state management, using Consumer/Consumer2 widgets for targeted rebuilds, accessing state with context.read/context.watch/context.select, setting up ChangeNotifierProvider or StateNotifierProvider, configuring MultiProvider for multiple providers, implementing ProxyProvider for dependent state, using Selector for optimized rebuilds, migrating from StatefulWidget to reactive state, maintaining legacy Provider codebases, debugging provider not found errors, preventing memory leaks with proper ChangeNotifier disposal, fixing context access issues in initState, or resolving .value constructor anti-patterns. Essential for apps with shared state across widgets (shopping carts, authentication, global settings, form state, theme management), when teaching state management fundamentals, or when debugging memory leaks and resource cleanup in Provider-based applications.

インストール方法を見る

含まれるファイル(3)

  • SKILL.md12.0 KB
  • references/provider.md10.1 KB
  • references/state-management-overview.md6.5 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Provider State Management Guide (v6.x)

Goal

Implement state management and dependency injection using the provider package in Flutter applications. Provider is built on Flutter's native InheritedWidget and remains the foundation for many production apps worldwide. This guide enforces MVVM (Model-View-ViewModel) architecture patterns and eliminates unnecessary widget rebuilds through targeted state consumption.

Process

Phase 1: Understand State Scope

Before implementing Provider, determine if the state truly needs to be shared:

  • Ephemeral State (local): Use StatefulWidget + setState() for UI state confined to a single widget (e.g., form input, animation progress, current tab index).
  • App State (shared): Use Provider when multiple unrelated widgets need access to the same data (e.g., user auth, shopping cart, app settings).

If uncertain about the scope, ask the user to clarify the intended lifecycle and accessibility requirements.

Phase 2: Install Dependencies

dependencies:
  provider: ^6.1.5

Phase 3: Implement MVVM Architecture

A. Create the Model Layer (Data / Repository)

Handle low-level data operations (HTTP requests, database queries, caching):

class UserRepository {
  Future<User> fetchUser(String id) async {
    // API call or database query
    return User(id: id, name: 'John Doe');
  }
}

Constraints: Models MUST NOT reference ChangeNotifier, BuildContext, or any UI code.

B. Create the ViewModel Layer

Extend ChangeNotifier to hold UI state and expose commands:

class UserViewModel extends ChangeNotifier {
  final UserRepository _repository;
  
  UserViewModel(this._repository);

  User? _user;
  User? get user => _user;

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  String? _errorMessage;
  String? get errorMessage => _errorMessage;

  // Command invoked by View
  Future<void> loadUser(String id) async {
    _isLoading = true;
    _errorMessage = null;
    notifyListeners(); // Trigger loading UI

    try {
      _user = await _repository.fetchUser(id);
    } catch (e) {
      _errorMessage = e.toString();
    } finally {
      _isLoading = false;
      notifyListeners(); // Trigger success/error UI
    }
  }
}

Critical: Always call notifyListeners() after state mutations to trigger widget rebuilds.

C. Provide State to Widget Tree

Use MultiProvider to inject dependencies at the root or page level:

void main() {
  runApp(
    MultiProvider(
      providers: [
        Provider(create: (_) => UserRepository()),
        ChangeNotifierProvider(
          create: (context) => UserViewModel(
            context.read<UserRepository>(),
          ),
        ),
      ],
      child: const MyApp(),
    ),
  );
}

Best Practice: Provide state as close to its usage point as possible. Avoid putting every provider at the app root if only used in specific screens.

D. Consume State in View Layer

For reading state in build() method:

Use Consumer or Selector to minimize rebuild scope:

class UserProfileView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Profile')),
      body: Consumer<UserViewModel>(
        builder: (context, viewModel, child) {
          if (viewModel.isLoading) {
            return Center(child: CircularProgressIndicator());
          }
          
          if (viewModel.errorMessage != null) {
            return Center(child: Text('Error: ${viewModel.errorMessage}'));
          }
          
          if (viewModel.user != null) {
            return Center(child: Text('Hello, ${viewModel.user!.name}'));
          }
          
          return Center(child: Text('No user loaded'));
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<UserViewModel>().loadUser('123'),
        child: Icon(Icons.refresh),
      ),
    );
  }
}

For executing commands (event handlers, callbacks):

Use context.read<T>() with listen: false:

// ✅ CORRECT: No rebuild triggered on button widget
onPressed: () => context.read<CartModel>().addItem(item),

// ❌ WRONG: Causes infinite rebuild loops
onPressed: () => Provider.of<CartModel>(context).addItem(item),

Phase 4: Memory Management & Resource Cleanup

⚠️ CRITICAL: .value vs create Constructor

This is the #1 cause of memory leaks in Provider 6.x applications.

Provider offers two constructors for providing objects. Understanding when to use each is essential:

Rule:

  • ✅ Use create for objects YOU create and manage (Provider will call dispose() automatically)
  • ❌ Use .value ONLY for pre-existing instances you manage elsewhere (Provider will NOT call dispose())

Anti-pattern (Memory Leak):

// ❌ WRONG: Creates a new instance but .value won't dispose it!
ChangeNotifierProvider.value(
  value: MyNotifier(), // MEMORY LEAK - never disposed!
  child: MyWidget(),
)

Why it leaks: The .value constructor assumes you're passing in an instance that already exists and will be disposed elsewhere. Creating a new instance here means it will never be cleaned up.

Correct Pattern:

// ✅ CORRECT: create constructor manages lifecycle
ChangeNotifierProvider(
  create: (_) => MyNotifier(),
  child: MyWidget(),
)

When to actually use .value:

// ✅ CORRECT: Reusing an existing instance from parent scope
final existingNotifier = context.watch<MyNotifier>();

return ChangeNotifierProvider.value(
  value: existingNotifier, // Pre-existing instance
  child: ChildWidget(),
)

ChangeNotifier Disposal Checklist

Every ChangeNotifier subclass MUST implement dispose() and clean up all resources:

class MyViewModel extends ChangeNotifier {
  StreamSubscription? _subscription;
  Timer? _timer;
  TextEditingController? _controller;
  
  MyViewModel() {
    _subscription = someStream.listen(_onData);
    _timer = Timer.periodic(Duration(seconds: 1), _onTick);
    _controller = TextEditingController();
  }
  
  @override
  void dispose() {
    // Cancel ALL subscriptions
    _subscription?.cancel();
    
    // Cancel ALL timers
    _timer?.cancel();
    
    // Dispose ALL controllers
    _controller?.dispose();
    
    // Close ALL streams you own
    // _myStreamController?.close();
    
    // ALWAYS call super.dispose() last
    super.dispose();
  }
}

Checklist - Before shipping, verify each ChangeNotifier cleans up:

  • StreamSubscriptions (.cancel())
  • Timers (.cancel())
  • TextEditingControllers (.dispose())
  • AnimationControllers (.dispose())
  • FocusNodes (.dispose())
  • ScrollControllers (.dispose())
  • StreamControllers you own (.close())
  • Any platform resources (camera, location services, etc.)

Testing for leaks: Use Flutter DevTools Memory tab to verify instances are released when navigating away from screens.

Context Access Patterns & Solutions

Problem: "Provider not found" or "Bad state" errors when accessing providers in initState.

// ❌ WRONG: Context not ready during initState
@override
void initState() {
  super.initState();
  final provider = context.watch<MyProvider>(); // Error!
}

Solution 1: Access providers in build() method

@override
Widget build(BuildContext context) {
  final provider = context.watch<MyProvider>(); // ✅ Works
  return ...;
}

Solution 2: Use didChangeDependencies lifecycle method

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  // Context is ready here
  final provider = context.read<MyProvider>();
  provider.loadInitialData();
}

Solution 3: Use context.read for one-time initialization

@override
void initState() {
  super.initState();
  // Schedule for after first frame
  WidgetsBinding.instance.addPostFrameCallback((_) {
    context.read<MyProvider>().initialize();
  });
}

Critical Distinction:

  • context.watch<T>() - Subscribes to changes, triggers rebuilds (use in build())
  • context.read<T>() - One-time access, no subscription (use in callbacks/lifecycle methods)
  • context.select<T, R>() - Subscribes to specific property (use in build() for granular rebuilds)

Phase 5: Advanced Patterns

Using Selector for Granular Rebuilds

When a widget only needs a small part of a large model:

Selector<UserModel, String>(
  selector: (context, user) => user.name,
  builder: (context, name, child) {
    // Only rebuilds when user.name changes
    return Text('Hello, $name');
  },
)

Using ProxyProvider for Dependent Services

When a ViewModel depends on another Provider:

MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (_) => AuthProvider()),
    ProxyProvider<AuthProvider, CartService>(
      update: (context, auth, previous) => CartService(auth.userId),
    ),
  ],
  child: MyApp(),
)

Reference Documentation

For detailed implementation guides:


Constraints

  • No Business Logic in Views: StatelessWidget and StatefulWidget MUST only contain UI and layout logic. All data transformation belongs in ViewModels.
  • Strict UDF: Data flows down (Repository → ViewModel → View). Events flow up (View → ViewModel → Repository). Views NEVER mutate Repository data directly.
  • Targeted Rebuilds: Never use Provider.of<T>(context) with listen: true at the root of large build methods. Use Consumer<T> or Selector<T, R> to scope rebuilds.
  • Command Invocation: When calling ViewModel methods from event handlers, MUST use context.read<T>() or Provider.of<T>(context, listen: false).
  • No context.watch in initState: Prohibited from using watch inside initState(). Use context.read instead or didChangeDependencies().
  • ChangeNotifier Separation: ViewModels MUST NOT contain UI elements, rendering code, or BuildContext references. This ensures testability.
  • Mandatory Disposal: Every ChangeNotifier MUST implement dispose() and clean up all subscriptions, timers, controllers, and resources. Use create constructor (not .value) when creating new instances.
  • Memory Leak Prevention: NEVER use ChangeNotifierProvider.value(value: NewInstance()). Always use ChangeNotifierProvider(create: (_) => NewInstance()) for proper lifecycle management.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Declarative animation patterns using flutter_animate v4.5.x with composable effects (fade, scale, slide, shimmer, shake, blur), GLSL fragment shaders, and scroll-synchronized animations. Use this skill for adding UI animations and transitions, building staggered or sequenced effects, creating custom animation effects, fixing animation jank or performance issues, implementing shimmer loading states, or adding scroll-driven animations. Supports delays, curves, adaptive effects, and easy extension method chaining (.animate()).

日本語の概要は準備中です。原文の説明を表示しています。

Poorgramer-Zack/dart-expert-skills72026年8月11日 更新

Dart 3 modern features (through 3.10) and Effective Dart best practices including records, patterns, sealed classes, dot shorthands, null-aware collection elements, and wildcard variables. Use when adopting Dart 3.7+ syntax, using dot shorthands for enums or static members, using null-aware elements in lists or maps, using wildcard _ parameters, improving code quality, or reviewing Dart code conventions.

日本語の概要は準備中です。原文の説明を表示しています。

Poorgramer-Zack/dart-expert-skills72026年8月11日 更新

Expert-level guidance for Flutter production applications: architecture decisions (Clean/Layered/Hexagonal/Feature-First), advanced state management (BLoC/Riverpod/GetX), performance optimization (eliminating jank, memory profiling, reducing rebuild overhead), code review with design pattern enforcement, scalability assessments, native platform integration (MethodChannel/EventChannel/FFI), complex UI challenges (custom render objects, slivers), testing strategies (unit/widget/integration/golden), CI/CD pipeline architecture, build optimization, and accessibility compliance. Triggers: 'architecture decision', 'design pattern', 'performance issue', 'code review', 'production problem', 'scaling concerns', 'best practices', 'technical debt', 'refactoring strategy'. Essential for complex enterprise apps, critical production debugging, architectural refactors, or when choosing between competing technical approaches.

日本語の概要は準備中です。原文の説明を表示しています。

Poorgramer-Zack/dart-expert-skills72026年8月11日 更新

Builds SEO-friendly web applications with Dart using the Jaspr framework. Use when creating Dart-based websites, blogs, or landing pages requiring semantic HTML output; implementing server-side rendering (SSR) or static site generation (SSG); adding Tailwind CSS via jaspr_tailwind; routing with jaspr_router; testing components with jaspr_test; or building full-stack Dart web apps where Flutter Web's canvas rendering is unsuitable.

日本語の概要は準備中です。原文の説明を表示しています。

Poorgramer-Zack/dart-expert-skills72026年8月11日 更新

shadcn_flutter UI component library for building accessible, themed Flutter interfaces. Use when integrating shadcn components, configuring design system theming, or building consistent widget layouts.

日本語の概要は準備中です。原文の説明を表示しています。

Poorgramer-Zack/dart-expert-skills72026年8月11日 更新

Configures Codemagic CI/CD pipelines for Flutter using `codemagic.yaml`. Use when setting up automated Flutter builds on Codemagic, configuring iOS code signing without Fastlane Match, deploying to App Store Connect or Google Play from Codemagic, caching pub dependencies in Codemagic, setting up encrypted environment variable groups, running tests with Codemagic's test dashboard, or choosing between Codemagic and GitHub Actions + Fastlane for a Flutter CI/CD pipeline.

日本語の概要は準備中です。原文の説明を表示しています。

Poorgramer-Zack/dart-expert-skills72026年8月11日 更新

Poorgramer-Zack のスキルをすべて見る

このスキルの問題を報告する