flutter-state-management

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

flutter-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 (
setState
,
ChangeNotifier
, or the
provider
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).
使用单向数据流(UDF)和模型-视图-视图模型(MVVM)设计模式,在Flutter应用中实现健壮的状态管理和架构模式。评估状态复杂度,区分临时(本地)状态和应用(共享)状态,应用合适的机制(
setState
ChangeNotifier
provider
包)。确保UI始终是不可变状态的纯函数,数据层作为唯一数据源(SSOT)。

Instructions

使用说明

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
    PageView
    , animation progress, local form input)
    • Yes: Use Ephemeral State (
      StatefulWidget
      +
      setState
      ).
  • 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
      ).
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
      +
      provider
      的MVVM架构)。
请停止并询问用户: 如果根据提供的需求无法明确判断状态范围(临时状态vs全局应用状态),请暂停操作,要求用户澄清数据的预期作用范围和生命周期。

2. Implement Ephemeral State (If Applicable)

2. 实现临时状态(如适用)

For local UI state, use a
StatefulWidget
. Ensure that
setState()
is called immediately when the internal state is modified to mark the widget as dirty and schedule a rebuild.
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状态,使用
StatefulWidget
。确保修改内部状态后立即调用
setState()
,将组件标记为待更新并调度重绘。
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
ChangeNotifier
. The ViewModel converts app data into UI state and exposes commands (methods) for the View to invoke.
dart
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(逻辑层) 继承
ChangeNotifier
。ViewModel将应用数据转换为UI状态,并暴露可供View调用的命令(方法)。
dart
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
provider
package to inject the ViewModel into the widget tree above the widgets that require access to it.
dart
void main() {
  runApp(
    MultiProvider(
      providers: [
        Provider(create: (_) => UserRepository()),
        ChangeNotifierProvider(
          create: (context) => UserViewModel(
            userRepository: context.read<UserRepository>(),
          ),
        ),
      ],
      child: const MyApp(),
    ),
  );
}
使用
provider
包将ViewModel注入到需要访问该状态的组件的上层节点。
dart
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
Consumer
to rebuild only the specific parts of the UI that depend on the state.
dart
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
Consumer
is placed as deep in the widget tree as possible to prevent unnecessary rebuilds of large widget subtrees.
基于ViewModel的状态构建UI,使用
Consumer
仅重绘UI中依赖该状态的特定部分。
dart
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),
      ),
    );
  }
}
校验与修复: 确认
Consumer
尽可能放在组件树的深层位置,避免大型子组件树不必要的重绘。

Constraints

约束条件

  • No Business Logic in Views:
    StatelessWidget
    and
    StatefulWidget
    classes must only contain UI, layout, and routing logic. All data transformation and business logic MUST reside in the ViewModel.
  • 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
    Provider.of<T>(context)
    with
    listen: true
    at the root of a large
    build
    method if only a small child needs the data. Use
    Consumer<T>
    or
    Selector<T, R>
    to scope rebuilds.
  • Command Invocation: When calling a ViewModel method from an event handler (e.g.,
    onPressed
    ), you MUST use
    context.read<T>()
    or
    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
    StatefulWidget
    类只能包含UI、布局和路由逻辑,所有数据转换和业务逻辑必须放在ViewModel中。
  • 严格的单向数据流: 数据必须向下流动(Repository -> ViewModel -> View),事件必须向上流动(View -> ViewModel -> Repository),视图不能直接修改Repository的数据。
  • 唯一数据源: 数据层(Repositories)必须是数据修改的唯一持有者,ViewModel仅负责格式化和存储该数据的UI展示版本。
  • 定向重绘: 如果只有很小的子组件需要数据,不要在大型
    build
    方法的根节点使用
    listen: true
    Provider.of<T>(context)
    ,使用
    Consumer<T>
    Selector<T, R>
    限制重绘范围。
  • 命令调用: 从事件处理函数(例如
    onPressed
    )调用ViewModel方法时,必须使用
    context.read<T>()
    或者
    Provider.of<T>(context, listen: false)
  • 不可变性: 传递给UI的数据模型需视为不可变,如果数据发生变更,ViewModel必须获取/创建新的实例并调用
    notifyListeners()