Flutter中BlocBuilder找不到Provider及BlocProvider创建未执行问题
我开发的Flutter应用包含HomePage和AuthScreen(登录注册)两个路由,main函数通过Provider实现依赖注入。HomeScreen中的BlocProvider可正常工作,但AuthScreenWidget存在两个问题:
- BlocProvider的create方法未执行
- 出现错误:
Could not find the correct Provider<StateStreamable<Object?>> above this BlocBuilder<StateStreamable<Object?>, Object?> Widget
相关代码
main函数
Future<void> main() async { JustAudioBackground.init( androidNotificationChannelId: 'com.ryanheise.bg_demo.channel.audio', androidNotificationChannelName: 'Audio playback', androidNotificationOngoing: true, ); runApp(Provider<Repository>( create: (context) => Repository( RemoteDataSource(ApiService.dioInstance()), LocalDataSource()), child: MyApp(), )); }
MyApp
class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); static const defaultFontFamily = 'iran_sans'; @override Widget build(BuildContext context) { const primaryTextColor = Color(0xff0D253C); const secondaryTextColor = Color(0xff2D4379); return Builder( builder: (context) { return MaterialApp( initialRoute: '/auth', routes: { '/': (context) => const HomeScreen(), '/auth': (context) => AuthScreenWidget(), }, title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, textTheme: const TextTheme( subtitle1: TextStyle( fontFamily: defaultFontFamily, color: Colors.white, fontWeight: FontWeight.w500, fontSize: 16), subtitle2: TextStyle( fontFamily: defaultFontFamily, color: Colors.white, fontWeight: FontWeight.w700, fontSize: 18), headline5: TextStyle( fontFamily: defaultFontFamily, color: Colors.black, fontWeight: FontWeight.w700, fontSize: 16), headline6: TextStyle( fontFamily: defaultFontFamily, fontWeight: FontWeight.bold, fontSize: 18, color: primaryTextColor), headline4: TextStyle( fontFamily: defaultFontFamily, fontWeight: FontWeight.w700, fontSize: 24, color: primaryTextColor), bodyText2: TextStyle( fontFamily: defaultFontFamily, color: secondaryTextColor, fontSize: 12), bodyText1: TextStyle( fontFamily: defaultFontFamily, color: Color.fromARGB(255, 33, 0, 122), fontSize: 20), ), ), builder: EasyLoading.init(), ); } ); } }
AuthScreenWidget
class AuthScreenWidget extends StatefulWidget { @override State<AuthScreenWidget> createState() => AuthScreenWidgetState(); } class AuthScreenWidgetState extends State<AuthScreenWidget> { String dropdownValue = '+98'; late final TextEditingController phoneNumber; @override void initState() { super.initState(); phoneNumber = TextEditingController(); } @override void dispose() { phoneNumber.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Builder( builder: (context) { return BlocProvider<AuthBloc>( create: (context) { final authBloc = AuthBloc( context.read<Repository>(), phoneNumber.text); authBloc.add(AuthStarted()); return authBloc; }, child: Scaffold( body: Column( children: [ Expanded( child: Padding( padding: EdgeInsets.fromLTRB(18, 18, 18, 0), child: Align( alignment: Alignment.bottomCenter, child: Directionality( textDirection: TextDirection.rtl, child: TextField( controller: phoneNumber, keyboardType: TextInputType.phone, style: TextStyle(color: Colors.black), decoration: const InputDecoration( border: OutlineInputBorder(), hintText: 'شماره تلفن', contentPadding: EdgeInsets.all(18), icon: Icon(CupertinoIcons.phone)), ), ), ), ), ), Padding( padding: const EdgeInsets.fromLTRB(18, 18, 18, 18), child: Align( alignment: Alignment.bottomCenter, child: Directionality( textDirection: TextDirection.rtl, child: SizedBox( width: double.infinity, height: 53, child: ElevatedButton( style: ButtonStyle( shape: MaterialStateProperty.all< RoundedRectangleBorder>( RoundedRectangleBorder( borderRadius: BorderRadius.circular(18.0), ), ), ), child: const Text( 'ورود', ), onPressed: () { if (phoneNumber.text != "") { BlocProvider<AuthBloc>( create: (BuildContext context) { final authBloc = AuthBloc( context.read<Repository>(), phoneNumber.text); authBloc.add(AuthStarted()); return authBloc; }); } }, ), ), ), ), ), Container( width: double.infinity, child: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(0, 0, 0, 32), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: const [ Text("ورود با ایمیل"), Icon(Icons.email, size: 20), ], ), ), ], ), ), DropdownButton<String>( value: dropdownValue, icon: const Icon(Icons.phone), elevation: 16, style: const TextStyle(color: Colors.black), onChanged: (String? newValue) { setState(() { dropdownValue = newValue!; }); }, items: <String>['+98'] .map<DropdownMenuItem<String>>((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), ), BlocBuilder(builder: (context, state) { if (state is AuthSuccess) { print("success auth"); return Container(); } return Container(); }) ], ), ), ); } ); } }
错误详情
Error: Could not find the correct Provider<StateStreamable<Object?>> above this BlocBuilder<StateStreamable<Object?>, Object?> Widget This happens because you used a `BuildContext` that does not include the provider of your choice. There are a few common scenarios: - You added a new provider in your `main.dart` and performed a hot-reload. To fix, perform a hot-restart. - The provider you are trying to read is in a different route. Providers are "scoped". So if you insert of provider inside a route, then other routes will not be able to access that provider. - You used a `BuildContext` that is an ancestor of the provider you are trying to read. Make sure that BlocBuilder<StateStreamable<Object?>, Object?> is under your MultiProvider/Provider<StateStreamable<Object?>>. This usually happens when you are creating a provider and trying to read it immediately. For example, instead of:
Widget build(BuildContext context) {
return Provider
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because context is associated
// to the widget that is the parent of Provider<Example>
child: Text(context.watch
);
}
consider using `builder` like so:
Widget build(BuildContext context) {
return Provider
create: (_) => Example(),
// we use builder to obtain a new BuildContext that has access to the provider
builder: (context, child) {
// No longer throws
return Text(context.watch
}
);
}
解决方案
1. 给BlocBuilder指定明确的泛型类型
当前BlocBuilder未声明对应的Bloc和State类型,导致框架无法定位到AuthBloc。修改为:
BlocBuilder<AuthBloc, AuthState>(builder: (context, state) { if (state is AuthSuccess) { print("success auth"); return Container(); } return Container(); })
2. 修复BlocProvider的作用域与BuildContext问题
外层的Builder是多余的,且原代码中BlocBuilder使用的是BlocProvider的父context,无法访问到BlocProvider。需要将BlocProvider的child替换为builder,让子组件能获取包含BlocProvider的context:
@override Widget build(BuildContext context) { return BlocProvider<AuthBloc>( create: (context) { final authBloc = AuthBloc( context.read<Repository>(), phoneNumber.text ); authBloc.add(AuthStarted()); return authBloc; }, builder: (context, child) { return Scaffold( body: Column( children: [ // ... 其他组件保持不变 // 这里的context已经包含BlocProvider BlocBuilder<AuthBloc, AuthState>(builder: (context, state) { if (state is AuthSuccess) { print("success auth"); return Container(); } return Container(); }) ], ), ); }, ); }
3. 修复按钮点击事件中的错误逻辑
按钮点击时创建BlocProvider但未将其加入widget树,完全无效。应该直接获取已存在的AuthBloc并触发事件:
onPressed: () { if (phoneNumber.text.isNotEmpty) { // 获取当前已存在的AuthBloc final authBloc = context.read<AuthBloc>(); // 传递手机号并触发登录事件(需确保AuthStarted事件支持接收手机号参数) authBloc.add(AuthStarted(phoneNumber: phoneNumber.text)); } }
4. 解决初始化时手机号为空的问题
initState时phoneNumber是空字符串,此时创建的AuthBloc会传入空值。可以:
- 延迟AuthStarted事件的触发,直到用户输入手机号后再执行
- 监听TextField的变化,动态更新AuthBloc的状态
- 在按钮点击时重新创建AuthBloc(需注意旧Bloc的资源释放)
内容的提问来源于stack exchange,提问作者Ramin

