flutter-state-management
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseflutter-state-management
flutter-state-management
Goal
目标
Implements robust state management and architectural patterns in Flutter applications using Unidirectional Data Flow (UDF) and the Model-View-ViewModel (MVVM) design pattern. Evaluates state complexity to differentiate between ephemeral (local) state and app (shared) state, applying the appropriate mechanisms (, , or the package). Ensures that the UI remains a pure function of immutable state and that the data layer acts as the Single Source of Truth (SSOT).
setStateChangeNotifierprovider使用单向数据流(UDF)和模型-视图-视图模型(MVVM)设计模式,在Flutter应用中实现健壮的状态管理和架构模式。评估状态复杂度,区分临时(本地)状态和应用(共享)状态,应用合适的机制(、 或 包)。确保UI始终是不可变状态的纯函数,数据层作为唯一数据源(SSOT)。
setStateChangeNotifierproviderInstructions
使用说明
1. Analyze State Requirements (Decision Logic)
1. 分析状态需求(决策逻辑)
Evaluate the data requirements of the feature to determine the appropriate state management approach. Use the following decision tree:
- Does the state only affect a single widget and its immediate children? (e.g., current page in a , animation progress, local form input)
PageView- Yes: Use Ephemeral State (+
StatefulWidget).setState
- Yes: Use Ephemeral State (
- Does the state need to be accessed by multiple unrelated widgets, or persist across different screens/sessions? (e.g., user authentication, shopping cart, global settings)
- Yes: Use App State (MVVM with +
ChangeNotifier).provider
- Yes: Use App State (MVVM with
STOP AND ASK THE USER: If the scope of the state (ephemeral vs. app-wide) is ambiguous based on the provided requirements, pause and ask the user to clarify the intended scope and lifecycle of the data.
评估功能的数据需求,确定合适的状态管理方案。使用以下决策树:
- 该状态是否仅影响单个组件及其直接子组件?(例如的当前页码、动画进度、本地表单输入)
PageView- 是: 使用临时状态(+
StatefulWidget)。setState
- 是: 使用临时状态(
- 该状态是否需要被多个无关联的组件访问,或者需要在不同页面/会话之间持久化?(例如用户身份认证状态、购物车、全局设置)
- 是: 使用应用状态(结合+
ChangeNotifier的MVVM架构)。provider
- 是: 使用应用状态(结合
请停止并询问用户: 如果根据提供的需求无法明确判断状态范围(临时状态vs全局应用状态),请暂停操作,要求用户澄清数据的预期作用范围和生命周期。
2. Implement Ephemeral State (If Applicable)
2. 实现临时状态(如适用)
For local UI state, use a . Ensure that is called immediately when the internal state is modified to mark the widget as dirty and schedule a rebuild.
StatefulWidgetsetState()dart
class LocalStateWidget extends StatefulWidget {
const LocalStateWidget({super.key});
State<LocalStateWidget> createState() => _LocalStateWidgetState();
}
class _LocalStateWidgetState extends State<LocalStateWidget> {
bool _isToggled = false;
void _handleToggle() {
// Validate-and-Fix: Ensure setState wraps the mutation.
setState(() {
_isToggled = !_isToggled;
});
}
Widget build(BuildContext context) {
return Switch(
value: _isToggled,
onChanged: (value) => _handleToggle(),
);
}
}对于本地UI状态,使用。确保修改内部状态后立即调用,将组件标记为待更新并调度重绘。
StatefulWidgetsetState()dart
class LocalStateWidget extends StatefulWidget {
const LocalStateWidget({super.key});
State<LocalStateWidget> createState() => _LocalStateWidgetState();
}
class _LocalStateWidgetState extends State<LocalStateWidget> {
bool _isToggled = false;
void _handleToggle() {
// Validate-and-Fix: Ensure setState wraps the mutation.
setState(() {
_isToggled = !_isToggled;
});
}
Widget build(BuildContext context) {
return Switch(
value: _isToggled,
onChanged: (value) => _handleToggle(),
);
}
}3. Implement App State using MVVM and UDF
3. 基于MVVM和UDF实现应用状态
For shared state, implement the MVVM pattern enforcing Unidirectional Data Flow (UDF).
A. Create the Model (Data Layer / SSOT)
Handle low-level tasks (HTTP, caching) in a Repository class.
dart
class UserRepository {
Future<User> fetchUser(String id) async {
// Implementation for fetching user data
}
}B. Create the ViewModel (Logic Layer)
Extend . The ViewModel converts app data into UI state and exposes commands (methods) for the View to invoke.
ChangeNotifierdart
class UserViewModel extends ChangeNotifier {
UserViewModel({required this.userRepository});
final UserRepository userRepository;
User? _user;
User? get user => _user;
bool _isLoading = false;
bool get isLoading => _isLoading;
String? _errorMessage;
String? get errorMessage => _errorMessage;
// Command invoked by the UI
Future<void> loadUser(String id) async {
_isLoading = true;
_errorMessage = null;
notifyListeners(); // Trigger loading UI
try {
_user = await userRepository.fetchUser(id);
} catch (e) {
_errorMessage = e.toString();
} finally {
_isLoading = false;
notifyListeners(); // Trigger success/error UI
}
}
}对于共享状态,实现遵循单向数据流(UDF)的MVVM模式。
A. 创建Model(数据层/唯一数据源)
在Repository类中处理底层任务(HTTP请求、缓存)。
dart
class UserRepository {
Future<User> fetchUser(String id) async {
// Implementation for fetching user data
}
}B. 创建ViewModel(逻辑层)
继承。ViewModel将应用数据转换为UI状态,并暴露可供View调用的命令(方法)。
ChangeNotifierdart
class UserViewModel extends ChangeNotifier {
UserViewModel({required this.userRepository});
final UserRepository userRepository;
User? _user;
User? get user => _user;
bool _isLoading = false;
bool get isLoading => _isLoading;
String? _errorMessage;
String? get errorMessage => _errorMessage;
// Command invoked by the UI
Future<void> loadUser(String id) async {
_isLoading = true;
_errorMessage = null;
notifyListeners(); // Trigger loading UI
try {
_user = await userRepository.fetchUser(id);
} catch (e) {
_errorMessage = e.toString();
} finally {
_isLoading = false;
notifyListeners(); // Trigger success/error UI
}
}
}4. Provide State to the Widget Tree
4. 向组件树提供状态
Use the package to inject the ViewModel into the widget tree above the widgets that require access to it.
providerdart
void main() {
runApp(
MultiProvider(
providers: [
Provider(create: (_) => UserRepository()),
ChangeNotifierProvider(
create: (context) => UserViewModel(
userRepository: context.read<UserRepository>(),
),
),
],
child: const MyApp(),
),
);
}使用包将ViewModel注入到需要访问该状态的组件的上层节点。
providerdart
void main() {
runApp(
MultiProvider(
providers: [
Provider(create: (_) => UserRepository()),
ChangeNotifierProvider(
create: (context) => UserViewModel(
userRepository: context.read<UserRepository>(),
),
),
],
child: const MyApp(),
),
);
}5. Consume State in the View (UI Layer)
5. 在View(UI层)中消费状态
Build the UI as a function of the ViewModel's state. Use to rebuild only the specific parts of the UI that depend on the state.
Consumerdart
class UserProfileView extends StatelessWidget {
const UserProfileView({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<UserViewModel>(
builder: (context, viewModel, child) {
if (viewModel.isLoading) {
return const 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 const Center(child: Text('No user loaded.'));
},
),
floatingActionButton: FloatingActionButton(
// Use listen: false when invoking commands outside the build method
onPressed: () => context.read<UserViewModel>().loadUser('123'),
child: const Icon(Icons.refresh),
),
);
}
}Validate-and-Fix: Verify that is placed as deep in the widget tree as possible to prevent unnecessary rebuilds of large widget subtrees.
Consumer基于ViewModel的状态构建UI,使用仅重绘UI中依赖该状态的特定部分。
Consumerdart
class UserProfileView extends StatelessWidget {
const UserProfileView({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<UserViewModel>(
builder: (context, viewModel, child) {
if (viewModel.isLoading) {
return const 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 const Center(child: Text('No user loaded.'));
},
),
floatingActionButton: FloatingActionButton(
// Use listen: false when invoking commands outside the build method
onPressed: () => context.read<UserViewModel>().loadUser('123'),
child: const Icon(Icons.refresh),
),
);
}
}校验与修复: 确认尽可能放在组件树的深层位置,避免大型子组件树不必要的重绘。
ConsumerConstraints
约束条件
- No Business Logic in Views: and
StatelessWidgetclasses must only contain UI, layout, and routing logic. All data transformation and business logic MUST reside in the ViewModel.StatefulWidget - Strict UDF: Data must flow down (Repository -> ViewModel -> View). Events must flow up (View -> ViewModel -> Repository). Views must never mutate Repository data directly.
- Single Source of Truth: The Data Layer (Repositories) must be the exclusive owner of data mutation. ViewModels only format and hold the UI representation of this data.
- Targeted Rebuilds: Never use with
Provider.of<T>(context)at the root of a largelisten: truemethod if only a small child needs the data. UsebuildorConsumer<T>to scope rebuilds.Selector<T, R> - Command Invocation: When calling a ViewModel method from an event handler (e.g., ), you MUST use
onPressedorcontext.read<T>().Provider.of<T>(context, listen: false) - Immutability: Treat data models passed to the UI as immutable. If data changes, the ViewModel must fetch/create a new instance and call .
notifyListeners()
- 视图中不能包含业务逻辑: 和
StatelessWidget类只能包含UI、布局和路由逻辑,所有数据转换和业务逻辑必须放在ViewModel中。StatefulWidget - 严格的单向数据流: 数据必须向下流动(Repository -> ViewModel -> View),事件必须向上流动(View -> ViewModel -> Repository),视图不能直接修改Repository的数据。
- 唯一数据源: 数据层(Repositories)必须是数据修改的唯一持有者,ViewModel仅负责格式化和存储该数据的UI展示版本。
- 定向重绘: 如果只有很小的子组件需要数据,不要在大型方法的根节点使用
build的listen: true,使用Provider.of<T>(context)或Consumer<T>限制重绘范围。Selector<T, R> - 命令调用: 从事件处理函数(例如)调用ViewModel方法时,必须使用
onPressed或者context.read<T>()。Provider.of<T>(context, listen: false) - 不可变性: 传递给UI的数据模型需视为不可变,如果数据发生变更,ViewModel必须获取/创建新的实例并调用。
notifyListeners()