You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Flutter setState实现转换为Bloc模式?求示例核心解析

基于实战示例讲解Flutter Bloc模式核心实现

我正在学习Flutter的Bloc模式,已阅读官方文档但仍未掌握其核心要点。了解到需将代码中的setState部分替换为Bloc且不改动布局,现提供一个基于setState的实战示例,希望基于该示例讲解Bloc模式的关键实现部分。

Bloc官方核心定义

  • Bloc是一个类:作为业务逻辑的容器,连接事件与状态。
  • 事件是对用户交互的响应:用户的点击、输入等操作都封装为事件。
  • 事件处理器负责将传入事件转换为零或多个输出状态:每个事件对应独立处理逻辑,输出新状态。
  • 状态间的变更称为Transition:从当前状态到新状态的完整转换过程。
  • 当新增事件且对应的事件处理器发出新状态时会发生Transition:事件触发后,处理器生成新状态,触发状态切换。
  • Transition包含当前状态、事件和下一个状态:可用于调试和追踪状态变化链路。
  • BlocProvider是向子组件提供BLoC的组件:通过它在组件树中共享Bloc实例。
  • Bloc绝不能直接发出新状态,所有状态变更必须在事件处理器中响应传入事件输出:状态变更只能通过emit方法在事件处理逻辑中完成。
  • builder函数根据状态返回组件:UI组件根据Bloc的当前状态自动更新。

原setState实现代码

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  static const String title = 'Flutter Sample';
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: title,
        home: Scaffold(
            appBar: AppBar(title: const Text(title)),
            body: const MyStatefulWidget()));
  }
}

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({super.key});
  @override
  State<MyStatefulWidget> createState() => MyStatefulWidgetState();
}

class MyStatefulWidgetState extends State<MyStatefulWidget> {
  bool light = true;
  List<String> names = [];
  TextEditingController nc = TextEditingController();
  void addItemToList() {
    setState(() {
      names.add(nc.text);
      getList();
    });
  }

  List<Widget> getList() {
    listWidget = [];
    listWidget = names.map((p) => Text(p)).toList();
    return listWidget;
  }

  void deleteItemFromList() {
    setState(() {
      if (names.isEmpty) return;
      names.removeLast();
    });
  }

  clearInput() {
    setState(() {
      nc.text = "";
    });
  }

  bool x = true;

  enableInput() {
    setState(() {
      x != x;
    });
  }

  List<Widget> listWidget = [];
  @override
  Widget build(BuildContext context) {
    Widget test1 = const Text("Hello Flutter");
    Widget test2 = Padding(
        padding: const EdgeInsets.all(20),
        child: TextField(
            enabled: x,
            controller: nc,
            decoration: const InputDecoration(
                border: OutlineInputBorder(), labelText: 'Name')));
    Widget test3 = SwitchListTile(
        value: x,
        onChanged: (bool value) {
          setState(() {
            x = value;
          });
        });
    Widget buttons = SizedBox(
      width: 1000,
      height: 30,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        mainAxisSize: MainAxisSize.min,
        children: [
          Expanded(
              child: TextButton(
                  onPressed: clearInput, child: const Text('ClearInput'))),
          Expanded(
              child: TextButton(
                  onPressed: addItemToList, child: const Text('Add'))),
          Expanded(
            child: TextButton(
              onPressed: deleteItemFromList,
              child: const Text('Remove'),
            ),
          )
        ],
      ),
    );

    Widget mylist = ListView.builder(
        scrollDirection: Axis.vertical,
        shrinkWrap: true,
        padding: const EdgeInsets.all(8),
        itemCount: names.length,
        itemBuilder: (BuildContext context, int index) {
          return listWidget[index];
        });
    return MaterialApp(
      home: Scaffold(
        resizeToAvoidBottomInset: false,
        body: SingleChildScrollView(
          scrollDirection: Axis.vertical,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Container(
                padding: const EdgeInsets.all(5),
                child: SingleChildScrollView(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: <Widget>[test1, test2, test3, buttons, mylist],
                  ),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

Bloc模式改造与核心实现讲解

1. 添加依赖

在pubspec.yaml中添加Bloc相关依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.1.3
  bloc: ^8.1.2

2. 定义事件(Event)

将所有用户操作封装为事件类,每个事件对应一种交互场景:

abstract class NameListEvent {}

class NameListAddEvent extends NameListEvent {
  final String name;
  NameListAddEvent(this.name);
}

class NameListRemoveEvent extends NameListEvent {}

class ClearInputEvent extends NameListEvent {}

class ToggleInputEnabledEvent extends NameListEvent {
  final bool isEnabled;
  ToggleInputEnabledEvent(this.isEnabled);
}

核心要点:事件是用户交互的抽象,携带必要业务数据(如添加的名字),Bloc通过接收事件触发状态变更。

3. 定义状态(State)

状态包含所有需要管理的业务数据,必须是不可变类:

class NameListState {
  final List<String> names;
  final bool isInputEnabled;

  const NameListState({
    required this.names,
    required this.isInputEnabled,
  });

  // 复制方法,用于生成新状态(保证不可变性)
  NameListState copyWith({
    List<String>? names,
    bool? isInputEnabled,
  }) {
    return NameListState(
      names: names ?? this.names,
      isInputEnabled: isInputEnabled ?? this.isInputEnabled,
    );
  }
}

核心要点:状态是UI的唯一数据源,每次变更都生成新的状态实例,避免状态污染。

4. 实现Bloc类

Bloc作为逻辑核心,接收事件并处理生成新状态:

import 'package:bloc/bloc.dart';

class NameListBloc extends Bloc<NameListEvent, NameListState> {
  // 初始状态:空列表,输入框启用
  NameListBloc() : super(const NameListState(names: [], isInputEnabled: true)) {
    // 注册添加事件处理器
    on<NameListAddEvent>((event, emit) {
      final newNames = List<String>.from(state.names)..add(event.name);
      emit(state.copyWith(names: newNames));
    });

    // 注册删除事件处理器
    on<NameListRemoveEvent>((event, emit) {
      if (state.names.isEmpty) return;
      final newNames = List<String>.from(state.names)..removeLast();
      emit(state.copyWith(names: newNames));
    });

    // 注册清空输入事件处理器(此处仅触发事件,UI侧直接操作Controller)
    on<ClearInputEvent>((event, emit) {});

    // 注册切换输入启用状态事件处理器
    on<ToggleInputEnabledEvent>((event, emit) {
      emit(state.copyWith(isInputEnabled: event.isEnabled));
    });
  }
}

核心要点:

  • Bloc是一个类,继承自Bloc<Event, State>
  • 通过on<Event>注册事件处理器,每个事件对应独立的处理逻辑
  • 状态变更必须通过emit方法发出新状态,不能直接修改原状态
  • 每次状态变更都会生成Transition,包含当前状态、事件、新状态

5. UI集成Bloc(不改动原布局)

用BlocProvider提供Bloc实例,用BlocBuilder监听状态更新,替换原setState逻辑:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  static const String title = 'Flutter Sample';
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(title: const Text(title)),
        // 用BlocProvider提供NameListBloc实例
        body: BlocProvider(
          create: (context) => NameListBloc(),
          child: const MyStatelessWidget(), // 原StatefulWidget改为Stateless
        ),
      ),
    );
  }
}

class MyStatelessWidget extends StatelessWidget {
  const MyStatelessWidget({super.key});
  final TextEditingController nc = TextEditingController();

  @override
  Widget build(BuildContext context) {
    // 获取Bloc实例
    final nameListBloc = BlocProvider.of<NameListBloc>(context);

    Widget test1 = const Text("Hello Flutter");
    
    // 用BlocBuilder监听状态,更新TextField的enabled属性
    Widget test2 = BlocBuilder<NameListBloc, NameListState>(
      builder: (context, state) {
        return Padding(
          padding: const EdgeInsets.all(20),
          child: TextField(
            enabled: state.isInputEnabled,
            controller: nc,
            decoration: const InputDecoration(
              border: OutlineInputBorder(),
              labelText: 'Name',
            ),
          ),
        );
      },
    );

    // 切换开关时触发ToggleInputEnabledEvent
    Widget test3 = BlocBuilder<NameListBloc, NameListState>(
      builder: (context, state) {
        return SwitchListTile(
          value: state.isInputEnabled,
          onChanged: (bool value) {
            nameListBloc.add(ToggleInputEnabledEvent(value));
          },
        );
      },
    );

    Widget buttons = SizedBox(
      width: 1000,
      height: 30,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        mainAxisSize: MainAxisSize.min,
        children: [
          Expanded(
            child: TextButton(
              onPressed: () {
                nc.text = "";
                nameListBloc.add(ClearInputEvent());
              },
              child: const Text('ClearInput'),
            ),
          ),
          Expanded(
            child: TextButton(
              onPressed: () {
                if (nc.text.isNotEmpty) {
                  nameListBloc.add(NameListAddEvent(nc.text));
                }
              },
              child: const Text('Add'),
            ),
          ),
          Expanded(
            child: TextButton(
              onPressed: () {
                nameListBloc.add(NameListRemoveEvent());
              },
              child: const Text('Remove'),
            ),
          )
        ],
      ),
    );

    // 用BlocBuilder监听names列表变化,更新ListView
    Widget mylist = BlocBuilder<NameListBloc, NameListState>(
      builder: (context, state) {
        return ListView.builder(
          scrollDirection: Axis.vertical,
          shrinkWrap: true,
          padding: const EdgeInsets.all(8),
          itemCount: state.names.length,
          itemBuilder: (BuildContext context, int index) {
            return Text(state.names[index]);
          },
        );
      },
    );

    return Scaffold(
      resizeToAvoidBottomInset: false,
      body: SingleChildScrollView(
        scrollDirection: Axis.vertical,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Container(
              padding: const EdgeInsets.all(5),
              child: SingleChildScrollView(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: <Widget>[test1, test2, test3, buttons, mylist],
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

核心要点:

  • BlocProvider在组件树中提供Bloc实例,子组件可通过BlocProvider.of获取
  • BlocBuilder根据当前状态自动重建对应的UI部分,替代原setState的作用
  • 用户操作不再直接修改状态,而是通过bloc.add(Event)触发事件,由Bloc处理后更新状态

内容的提问来源于stack exchange,提问作者DktPhl2018

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 08:37:02