Flutter中BlocListener报找不到TodosBloc的ProviderNotFoundException错误
开发Flutter待办事项应用、学习Bloc状态管理框架时,在新增待办页面调用BlocListener组件抛出异常,错误定位在AddToDo类中。
触发异常为ProviderNotFoundException:无法在BlocListener<TodosBloc, TodosState>组件的上层Widget树中找到匹配的Provider。
该异常本质是当前使用的BuildContext未包含目标Provider实例,常见触发场景分为三类:
- 在
main.dart中新增Provider配置后执行了热重载操作,这种情况直接执行热重启即可修复。 - 尝试读取的Provider注册在其他路由内:Provider具备作用域特性,若在某个路由内部注册Provider,通过
Navigator.push跳转的新路由属于同级路由,无法访问该路由内注册的Provider实例。 - 使用的
BuildContext属于待读取Provider的祖先节点上下文,没有处于Provider的子作用域范围内,常见于创建Provider后立即在同个context下读取它的场景。
注意:必须保证
BlocListener<TodosBloc, TodosState>位于MultiProvider/BlocProvider的子节点下,否则无法正常读取Bloc实例。
错误写法
直接在Provider的child属性中使用当前context读取Provider,此时context关联的是Provider的父级Widget,会抛出ProviderNotFound异常:
Widget build(BuildContext context) { return Provider<Example>( create: (_) => Example(), child: Text(context.watch<Example>().toString()), ); }
正确写法
使用builder获取处于Provider子作用域的新BuildContext,即可正常读取实例:
Widget build(BuildContext context) { return Provider<Example>( create: (_) => Example(), builder: (context, child) { return Text(context.watch<Example>().toString()); } ); }
根因
当前代码将TodosBloc通过MultiBlocProvider注册在MaterialApp的home属性对应的HomeScreen页面中,MultiBlocProvider是HomeScreen的父节点,作用域仅覆盖HomeScreen所在的路由。通过Navigator.push跳转至AddToDo页面时,新路由不在该Provider的作用域范围内,因此无法读取到已注册的TodosBloc实例。
修复方案
将MultiBlocProvider上移到MaterialApp的上层,作为整个应用的根节点包裹MaterialApp,这样所有路由都处于Bloc的作用域内,可以正常读取TodosBloc实例。
修改后的main.dart代码如下:
import 'package:bloc_practise/blocs/todos/todos_bloc.dart'; import 'package:bloc_practise/todo_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'home_screen.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { // 将MultiBlocProvider移到MaterialApp外层,全局注册Bloc return MultiBlocProvider( providers: [ BlocProvider( create: (context) => TodosBloc() ..add( LoadTodo( todos: [ Todo( id: '1', task: 'Sample Todo 1', description: 'This is a test Todo', ), Todo( id: '2', task: 'Sample Todo 2', description: 'This is a test Todo', ), ], ), ), ) ], child: MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( primaryColor: Color(0xff000A1F), appBarTheme: AppBarTheme( color: Color(0xff000A1F), ), ), home: HomeScreen(), ), ); } }
内容的提问来源于stack exchange,提问作者Pratul Pant

