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

Flutter BLoC开发Todo应用报错:BlocProvider.of()未找到TodoBloc上下文

Flutter BLoC Todo应用添加待办时的上下文错误修复

错误信息

BlocProvider.of() called with a context that does not contain a TodoBloc.

No ancestor could be found starting from the context that was passed to BlocProvider.of<TodoBloc>().

This can happen if the context you used comes from a widget above the BlocProvider.

The context used was: AddTodoScreen

问题根源

  1. 重复创建Bloc实例:AddTodoScreen中自行通过BlocProvider新建了TodoBloc,导致与主页面的Bloc实例隔离,无法共享状态。
  2. 上下文层级错误:调用BlocProvider.of<TodoBloc>(context)时使用的是AddTodoScreen的根上下文,该上下文在当前页面的BlocProvider之上,无法匹配到目标Bloc;即使移除重复的BlocProvider,也需确保上下文能访问到主页面的BlocProvider。
  3. Bloc数据不同步:TodoBloc构造函数返回带示例数据的TodoLoaded状态,但类内_todos列表初始为空,导致添加新todo时id计算出错。

修复步骤

1. 重构AddTodoScreen,移除重复BlocProvider

复用主页面已初始化的TodoBloc实例,无需新建:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:todo_app_flutter/bloc/todo_bloc.dart';

class AddTodoScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final titleController = TextEditingController();
    final descriptionController = TextEditingController();

    return Scaffold(
      appBar: AppBar(
        title: const Text('Add Todo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            TextFormField(
              controller: titleController,
              decoration: const InputDecoration(labelText: 'Title'),
            ),
            const SizedBox(height: 16.0),
            TextFormField(
              controller: descriptionController,
              decoration: const InputDecoration(labelText: 'Description'),
              maxLines: 3,
            ),
            const SizedBox(height: 16.0),
            ElevatedButton(
              onPressed: () {
                final title = titleController.text.trim();
                final description = descriptionController.text.trim();
                if (title.isNotEmpty && description.isNotEmpty) {
                  // 通过上下文获取主页面的TodoBloc实例
                  context.read<TodoBloc>().add(
                        AddTodo(title, description),
                      );
                  Navigator.pop(context);
                }
              },
              child: const Text('Add Todo'),
            ),
          ],
        ),
      ),
    );
  }
}

用context.read<TodoBloc>()替代BlocProvider.of<TodoBloc>(context),更简洁且能正确获取祖先层级的Bloc实例。

2. 修正TodoBloc的初始数据同步

确保Bloc内部列表与初始状态数据一致,同时修正id生成逻辑:

import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';

import '../models/todo.dart';

part 'todo_event.dart';
part 'todo_state.dart';

class TodoBloc extends Bloc<TodoEvent, TodoState> {
  List<Todo> _todos = [];

  TodoBloc() : super(const TodoInitial()) {
    // 初始化示例数据到内部列表
    _todos = [
      Todo(id: 1, title: 'Sample Todo 1', description: 'Description 1'),
      Todo(id: 2, title: 'Sample Todo 2', description: 'Description 2'),
    ];
    // 发送加载事件初始化状态
    add(LoadTodos(_todos));
  }

  @override
  Stream<TodoState> mapEventToState(TodoEvent event,) async* {
    if (event is LoadTodos) {
      yield TodoLoaded(List.from(_todos));
    } else if (event is AddTodo) {
      final newTodo = Todo(
        // 基于最后一个todo的id生成新id,避免重复
        id: _todos.isNotEmpty ? _todos.last.id + 1 : 1,
        title: event.title,
        description: event.description,
      );
      _todos.add(newTodo);
      yield TodoLoaded(List.from(_todos));
    } else if (event is ToggleTodo) {
      final todoIndex = _todos.indexWhere((todo) => todo.id == event.id);
      if (todoIndex != -1) {
        // 可在此添加状态切换逻辑(如isCompleted)
        _todos[todoIndex] = _todos[todoIndex].copyWith();
        yield TodoLoaded(List.from(_todos));
      }
    } else if (event is DeleteTodo) {
      _todos.removeWhere((todo) => todo.id == event.id);
      yield TodoLoaded(List.from(_todos));
    }
  }
}

同时修正todo_event.dart中LoadTodos的构造函数(原代码缺少大括号会报错):

class LoadTodos extends TodoEvent {
  final List<Todo> todos;
  
  LoadTodos(this.todos);

  @override
  List<Object?> get props => [todos];
}

3. 完善TodoScreen的初始状态处理

在TodoScreen的BlocBuilder中,初始状态时触发数据加载:

// TodoScreen的BlocBuilder部分
if (state is TodoInitial) {
  context.read<TodoBloc>().add(LoadTodos([]));
  return const CircularProgressIndicator();
}

修复后逻辑说明

  • 主页面的TodoBloc由main.dart中的BlocProvider全局提供,所有子页面可通过上下文获取同一个实例,保证状态共享。
  • context.read<TodoBloc>()适用于无需监听状态变化的场景(如按钮点击事件),简化Bloc实例获取流程。
  • TodoBloc内部数据与状态同步,避免添加新todo时出现id重复或数据不一致问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 02:44:52