Install
npx -y skills add flutter/agent-plugins --skill flutter-managing-state --agent claude-codeRun in a terminal. Claude loads the skill when a task calls for it. Check the repo's README before you run it.
Files
Managing State in Flutter
Contents
- Core Concepts
- Architecture and Data Flow
- Workflow: Selecting a State Management Approach
- Workflow: Implementing MVVM with Provider
- Examples
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 aStatefulWidgetandsetState(). - 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, theproviderpackage, 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
ChangeNotifierand callnotifyListeners()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):
- Subclass
StatefulWidgetandState. - Store mutable state as private fields within the
Stateclass. - Mutate state exclusively inside a
setState()callback to mark the widget as dirty and schedule a rebuild.
- Subclass
- If managing App State (shared across widgets):
- Implement the MVVM pattern.
- Use the
providerpackage (a wrapper aroundInheritedWidget) to inject state into the widget tree. - Use
ChangeNotifierto 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 whennotifyListeners()is called. - Use
context.read<MyViewModel>()(orProvider.of<MyViewModel>(context, listen: false)) inside event handlers (likeonPressed) 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:
- Trigger a user action in the View.
- Verify the ViewModel receives the event and calls the Repository.
- Verify the Repository updates the SSOT and returns data.
- Verify the ViewModel updates its state and calls
notifyListeners(). - Verify the View rebuilds with the new state.
Run validator -> review errors -> fix missing
notifyListeners()calls or incorrectProviderscopes.
Examples
Ephemeral State Implementation (setState)
Use this pattern strictly for local, UI-only state.
class EphemeralCounter extends StatefulWidget {
const EphemeralCounter({super.key});
@override
State<EphemeralCounter> createState() => _EphemeralCounterState();
}
class _EphemeralCounterState extends State<EphemeralCounter> {
int _counter = 0; // Local state
void _increment() {
setState(() {
_counter++; // Mutate state and schedule rebuild
});
}
@override
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});
@override
Widget build(BuildContext context) {
// Inject ViewModel
return ChangeNotifierProvider(
create: (_) => CartViewModel(repository: CartRepository()),
child: const CartScreen(),
);
}
}
class CartScreen extends StatelessWidget {
const CartScreen({super.key});
@override
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),
),
);
}
}
Facts
- Kind
- Skill
- Repo
- flutter/agent-plugins
- Group
- Uncategorized
- Installs
- 9.6k
- Stars
- 3.1K
- 1find-skillsvercel-labs/skillsHelps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.3.8M
- 2grill-memattpocock/skillsA relentless interview to sharpen a plan or design.1.3M
- 3grill-with-docsmattpocock/skillsA relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.1.1M
- 4agent-browservercel-labs/agent-browserBrowser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.1.1M
- 5improve-codebase-architecturemattpocock/skillsScan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.1.1M
- 6tddmattpocock/skillsTest-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.1.1M