Productivity

flutter-managing-state

flutter/skills · updated Apr 8, 2026

$npx skills add https://github.com/flutter/skills --skill flutter-managing-state
summary

Manage ephemeral and app-level state in Flutter using StatefulWidget, MVVM, and Provider.

  • Distinguishes between ephemeral state (single widget, managed with setState() ) and app state (shared across widgets, managed with MVVM and provider package)
  • Implements unidirectional data flow with a single source of truth: Models handle data, ViewModels manage UI state via ChangeNotifier , Views consume and display state
  • Provides sequential workflow for MVVM implementation: define Repository,
skill.md

Managing State in Flutter

Contents

Core Concepts

Flutter's UI is declarative; it is built to reflect the current state of the app (UI = f(state)). When state changes, trigger a rebuild of the UI that depends on that state.

Distinguish between two primary types of state to determine your management strategy:

  • Ephemeral State (Local State): State contained neatly within a single widget (e.g., current page in a PageView, current selected tab, animation progress). Manage this using a StatefulWidget and setState().
  • App State (Shared State): State shared across multiple parts of the app and maintained between user sessions (e.g., user preferences, login info, shopping cart contents). Manage this using advanced approaches like InheritedWidget, the provider package, and the MVVM architecture.

Architecture and Data Flow

Implement the Model-View-ViewModel (MVVM) design pattern combined with Unidirectional Data Flow (UDF) for scalable app state management.

  • Unidirectional Data Flow (UDF): Enforce a strict flow where state flows down from the data layer, through the logic layer, to the UI layer. Events from user interactions flow up from the UI layer, to the logic layer, to the data layer.
  • Single Source of Truth (SSOT): Ensure data changes always happen in the data layer (Repositories). The SSOT class must be the only class capable of modifying its respective data.
  • Model (Data Layer): Handle low-level tasks like HTTP requests, data caching, and system resources using Repository classes.
  • ViewModel (Logic Layer): Manage the UI state. Convert app data from the Model into UI State. Extend ChangeNotifier and call notifyListeners() to trigger UI rebuilds when data changes.
  • View (UI Layer): Display the state provided by the ViewModel. Keep views lean; they should contain minimal logic (only routing, animations, or simple UI conditionals).

Workflow: Selecting a State Management Approach

Evaluate the scope of the state to determine the correct implementation strategy.

  • If managing Ephemeral State (single widget scope):
    1. Subclass StatefulWidget and State.
    2. Store mutable state as private fields within the State class.
    3. Mutate state exclusively inside a setState() callback to mark the widget as dirty and schedule a rebuild.
  • If managing App State (shared across widgets):
    1. Implement the MVVM pattern.
    2. Use the provider package (a wrapper around InheritedWidget) to inject state into the widget tree.
    3. Use ChangeNotifier to emit state updates.

Workflow: Implementing MVVM with Provider

Follow this sequential workflow to implement app-level state management using MVVM and provider.

Task Progress:

  • 1. Define the Model (Repository).
  • 2. Create the ViewModel (ChangeNotifier).
  • 3. Inject the ViewModel into the Widget Tree.
  • 4. Consume the State in the View.
  • 5. Validate the implementation.

1. Define the Model (Repository)

Create a repository class to act as the Single Source of Truth (SSOT) for the specific data domain. Handle all external API calls or database queries here.

2. Create the ViewModel (ChangeNotifier)

Create a ViewModel class that extends ChangeNotifier.

  • Pass the Repository into the ViewModel via dependency injection.
  • Define properties for the UI state (e.g., isLoading, data, errorMessage).
  • Implement methods to handle UI events. Inside these methods, mutate the state and call notifyListeners() to trigger UI rebuilds.

3. Inject the ViewModel into the Widget Tree

Use ChangeNotifierProvider from the provider package to provide the ViewModel to the widget subtree that requires it. Place the provider as low in the widget tree as possible to avoid polluting the scope.

4. Consume the State in the View

Access the ViewModel in your StatelessWidget or StatefulWidget.

  • Use Consumer<MyViewModel> to rebuild specific parts of the UI when notifyListeners() is called.
  • Use context.read<MyViewModel>() (or Provider.of<MyViewModel>(context, listen: false)) inside event handlers (like onPressed) to call ViewModel methods without triggering a rebuild of the calling widget.

5. Validate the implementation

Run the following feedback loop to ensure data flows correctly:

  1. Trigger a user action in the View.
  2. Verify the ViewModel receives the event and calls the Repository.
  3. Verify the Repository updates the SSOT and returns data.
  4. Verify the ViewModel updates its state and calls notifyListeners().
  5. Verify the View rebuilds with the new state. Run validator -> review errors -> fix missing notifyListeners() calls or incorrect Provider scopes.

Examples

Ephemeral State Implementation (setState)

Use this pattern strictly for local, UI-only state.

class EphemeralCounter extends StatefulWidget {
  const EphemeralCounter({super.key});

  
  State<EphemeralCounter> createState() => _EphemeralCounterState();
}

class _EphemeralCounterState extends State<EphemeralCounter> {
  int _counter = 0; // Local state

  void _increment() {
    setState(() {
      _counter++; // Mutate state and schedule rebuild
    });
  }

  
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: _increment,
      child: Text('Count: $_counter'),
    );
  }
}

App State Implementation (MVVM + Provider)

Use this pattern for shared data and complex business logic.

// 1. Model (Repository)
class CartRepository {
  Future<void> saveItemToCart(String item) async {
    // Simulate network/database call
    await Future.delayed(const Duration(milliseconds: 500));
  }
}

// 2. ViewModel (ChangeNotifier)
class CartViewModel extends ChangeNotifier {
  final CartRepository repository;
  
  CartViewModel({required this.repository});

  final List<String> _items = [];
  bool isLoading = false;
  String? errorMessage;

  List<String> get items => List.unmodifiable(_items);

  Future<void> addItem(String item) async {
    isLoading = true;
    errorMessage = null;
    notifyListeners(); // Trigger loading UI

    try {
      await repository.saveItemToCart(item);
      _items.add(item);
    } catch (e) {
      errorMessage = 'Failed to add item';
    } finally {
      isLoading = false;
      notifyListeners(); // Trigger success/error UI
    }
  }
}

// 3. Injection & 4. View (UI)
class CartApp extends StatelessWidget {
  const CartApp({super.key});

  
  Widget build(BuildContext context) {
    // Inject ViewModel
    return ChangeNotifierProvider(
      create: (_) => CartViewModel(repository: CartRepository()),
      child: const CartScreen(),
    );
  }
}

class CartScreen extends StatelessWidget {
  const CartScreen({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Consumer<CartViewModel>(
        builder: (context, viewModel, child) {
          if (viewModel.isLoading) {
            return const CircularProgressIndicator();
          }
          if (viewModel.errorMessage != null) {
            return Text(viewModel.errorMessage!);
          }
          return ListView.builder(
            itemCount: viewModel.items.length,
            itemBuilder: (_, index) => Text(viewModel.items[index]),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        // Use read() to access methods without listening for rebuilds
        onPressed: () => context.read<CartViewModel>().addItem('New Item'),
        child: const Icon(Icons.add),
      ),
    );
  }
}
general reviews

Ratings

4.510 reviews
  • Shikha Mishra· Oct 10, 2024

    flutter-managing-state is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.

  • Piyush G· Sep 9, 2024

    Keeps context tight: flutter-managing-state is the kind of skill you can hand to a new teammate without a long onboarding doc.

  • Chaitanya Patil· Aug 8, 2024

    Registry listing for flutter-managing-state matched our evaluation — installs cleanly and behaves as described in the markdown.

  • Sakshi Patil· Jul 7, 2024

    flutter-managing-state reduced setup friction for our internal harness; good balance of opinion and flexibility.

  • Ganesh Mohane· Jun 6, 2024

    I recommend flutter-managing-state for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.

  • Oshnikdeep· May 5, 2024

    Useful defaults in flutter-managing-state — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.

  • Dhruvi Jain· Apr 4, 2024

    flutter-managing-state has been reliable in day-to-day use. Documentation quality is above average for community skills.

  • Rahul Santra· Mar 3, 2024

    Solid pick for teams standardizing on skills: flutter-managing-state is focused, and the summary matches what you get after install.

  • Pratham Ware· Feb 2, 2024

    We added flutter-managing-state from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.

  • Yash Thakker· Jan 1, 2024

    flutter-managing-state fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.