Flutter跳转NdefWritePage时Provider<Repository>找不到错误求助
问题描述
从首页导航至NdefWritePage.withDependency()时抛出ProviderNotFoundException,提示无法找到正确的Provider<Repository>,错误触发于stream: Provider.of<NdefWriteModel>(context, listen: false).subscribe()行。
错误详情:
The following ProviderNotFoundException was thrown building NdefWritePage(dirty):
Error: Could not find the correct Provider<Repository> above this _InheritedProviderScope<NdefWriteModel?> WidgetThis happens because you used a
BuildContextthat does not include the provider
of your choice. There are a few common scenarios:
You added a new provider in your
main.dartand 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
BuildContextthat is an ancestor of the provider you are trying to read.Make sure that _InheritedProviderScope<NdefWriteModel?> is under your MultiProvider/Provider<Repository>.
This usually happens when you are creating a provider and trying to read it immediately.
关键代码片段
App相关代码
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../repository/repository.dart'; import 'about.dart'; import 'common/form_row.dart'; import 'ndef_format.dart'; import 'ndef_write.dart'; import 'ndef_write_lock.dart'; import 'tag_read.dart'; class App extends StatelessWidget { static Future<Widget> withDependency() async { final repo = await Repository.createInstance(); return MultiProvider( providers: [ Provider<Repository>.value( value: repo, ), ], child: App(), ); } @override Widget build(BuildContext context) { return MaterialApp( home: _Home(context: context), theme: _themeData(Brightness.light), darkTheme: _themeData(Brightness.dark), ); } } class _Home extends StatelessWidget { final BuildContext context; const _Home({super.key, required this.context}); @override Widget build(BuildContext context) { context = this.context; return Scaffold( appBar: AppBar( title: Text('NFC Manager'), ), body: ListView( padding: EdgeInsets.all(2), children: [ FormSection(children: [ FormRow( title: Text('Tag - Read'), trailing: Icon(Icons.chevron_right), onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => TagReadPage.withDependency(), )), ), FormRow( title: Text('Ndef - Write'), trailing: Icon(Icons.chevron_right), onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => NdefWritePage.withDependency(), )), ), FormRow( title: Text('Ndef - Write Lock'), trailing: Icon(Icons.chevron_right), onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => NdefWriteLockPage.withDependency(), )), ), if (Platform.isAndroid) FormRow( title: Text('Ndef - Format'), trailing: Icon(Icons.chevron_right), onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => NdefFormatPage.withDependency(), )), ), ]), FormSection(children: [ FormRow( title: Text('About'), trailing: Icon(Icons.chevron_right), onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => AboutPage(), )), ), ]), ], ), ); } } ThemeData _themeData(Brightness brightness) { return ThemeData( brightness: brightness, // Matches app icon color. primarySwatch: MaterialColor(0xFF4D8CFE, <int, Color>{ 50: Color(0xFFEAF1FF), 100: Color(0xFFCADDFF), 200: Color(0xFFA6C6FF), 300: Color(0xFF82AFFE), 400: Color(0xFF689DFE), 500: Color(0xFF4D8CFE), 600: Color(0xFF4684FE), 700: Color(0xFF3D79FE), 800: Color(0xFF346FFE), 900: Color(0xFF255CFD), }), appBarTheme: AppBarTheme( brightness: Brightness.dark, ), inputDecorationTheme: InputDecorationTheme( isDense: true, border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 12), errorStyle: TextStyle(height: 0.75), helperStyle: TextStyle(height: 0.75), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( minimumSize: Size.fromHeight(40), )), scaffoldBackgroundColor: brightness == Brightness.dark ? Colors.black : null, cardColor: brightness == Brightness.dark ? Color.fromARGB(255, 28, 28, 30) : null, dialogTheme: DialogTheme( backgroundColor: brightness == Brightness.dark ? Color.fromARGB(255, 28, 28, 30) : null, ), highlightColor: brightness == Brightness.dark ? Color.fromARGB(255, 44, 44, 46) : null, splashFactory: NoSplash.splashFactory, ); }
NdefWritePage相关代码
class NdefWriteModel { NdefWriteModel(this._repo); final Repository _repo; Stream<Iterable<WriteRecord>> subscribe() { return _repo.subscribeWriteRecordList(); } Future<void> delete(WriteRecord record) { return _repo.deleteWriteRecord(record); } Future<String?> handleTag( NfcTag tag, Iterable<WriteRecord> recordList) async { final tech = Ndef.from(tag); if (tech == null) throw ('Tag is not ndef.'); if (!tech.isWritable) throw ('Tag is not ndef writable.'); try { final message = NdefMessage(recordList.map((e) => e.record).toList()); await tech.write(message); } on PlatformException catch (e) { throw (e.message ?? 'Some error has occurred.'); } return '[Ndef - Write] is completed.'; } } class NdefWritePage extends StatelessWidget { static Widget withDependency() => ChangeNotifierProvider<NdefWriteModel>( create: (context) => NdefWriteModel(Provider.of(context, listen: false)), child: NdefWritePage(), ); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Ndef - Write'), ), body: StreamBuilder<Iterable<WriteRecord>>( stream: Provider.of<NdefWriteModel>(context, listen: false).subscribe(), builder: (context, ss) => ListView( padding: EdgeInsets.all(2), children: [ FormSection(children: [ FormRow( title: Text('Add Record'), trailing: Icon(Icons.chevron_right), onTap: () async { final result = await showDialog<String>( context: context, builder: (context) => SimpleDialog( title: Text('Record Types'), children: [ SimpleDialogOption( child: Text('Text'), onPressed: () => Navigator.pop(context, 'text'), ), SimpleDialogOption( child: Text('Uri'), onPressed: () => Navigator.pop(context, 'uri'), ), SimpleDialogOption( child: Text('Mime'), onPressed: () => Navigator.pop(context, 'mime'), ), SimpleDialogOption( child: Text('External'), onPressed: () => Navigator.pop(context, 'external'), ), ], ), ); switch (result) { case 'text': Navigator.push( context, MaterialPageRoute( fullscreenDialog: true, builder: (context) => EditTextPage.withDependency(), )); break; case 'uri': Navigator.push( context, MaterialPageRoute( fullscreenDialog: true, builder: (context) => EditUriPage.withDependency(), )); break; case 'mime': Navigator.push( context, MaterialPageRoute( fullscreenDialog: true, builder: (context) => EditMimePage.withDependency(), )); break; case 'external': Navigator.push( context, MaterialPageRoute( fullscreenDialog: true, builder: (context) => EditExternalPage.withDependency(), )); break; case null: break; default: throw ('unsupported: result=$result'); } }, ), FormRow( title: Text('Start Session', style: TextStyle( color: ss.data?.isNotEmpty != true ? Theme.of(context).disabledColor : Theme.of(context).colorScheme.primary, )), onTap: ss.data?.isNotEmpty != true ? null : () => startSession( context: context, handleTag: (tag) => Provider.of<NdefWriteModel>( context, listen: false) .handleTag(tag, ss.data!), ), ), ]), if (ss.data?.isNotEmpty == true) FormSection( header: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('RECORDS'), Text( '${ss.data!.map((e) => e.record.byteLength).reduce((a, b) => a + b)} bytes'), ], ), children: List.generate(ss.data!.length, (i) { final record = ss.data!.elementAt(i); return _WriteRecordFormRow(i, record); }), ), ], ), ), ); } }
解决方案
问题根源
- _Home组件的Context错误覆盖:在
_Home的build方法中,你将传入的context替换成了类成员的context,这个成员context是从App的build方法传递过来的,而App的build方法中的context并不包含MultiProvider提供的Repository(因为MultiProvider是App的父级)。 - NdefWritePage的Provider作用域问题:当导航到
NdefWritePage时,NdefWritePage.withDependency()中的ChangeNotifierProvider尝试从当前路由的context获取Repository,但这个context没有包含MultiProvider的Repository,因为MaterialApp会创建新的导航上下文,导致路由内无法访问MaterialApp外部的Provider。
修复步骤
步骤1:修复_Home组件的Context问题
删除_Home类中的context成员变量,直接使用build方法传入的context:
class _Home extends StatelessWidget { const _Home({super.key}); // 移除context参数 @override Widget build(BuildContext context) { // 移除context = this.context;这一行 return Scaffold( // 保持原有代码不变 ); } }
同时修改App的build方法:
@override Widget build(BuildContext context) { return MaterialApp( home: _Home(), // 不再传递context theme: _themeData(Brightness.light), darkTheme: _themeData(Brightness.dark), ); }
步骤2:确保Repository Provider在MaterialApp之上
当前MultiProvider已经在App的父级,这部分是正确的,但需要确保路由中的页面能够访问到这个Provider。另一种方式是将MultiProvider包裹在MaterialApp外部,不过当前结构已经满足,只要修复context问题即可。
步骤3:优化NdefWritePage的依赖获取(可选)
可以使用Consumer或select来获取NdefWriteModel,避免直接在build方法中调用Provider.of:
body: Consumer<NdefWriteModel>( builder: (context, model, child) { return StreamBuilder<Iterable<WriteRecord>>( stream: model.subscribe(), builder: (context, ss) => ListView( // 原有ListView代码 ), ); }, ),
步骤4:热重启应用
修改Provider相关代码后,热重载可能无法生效,需要执行热重启。
验证修复
完成上述修改后,导航到NdefWritePage时,NdefWriteModel能够正确获取到Repository实例,不会再抛出ProviderNotFoundException。
内容的提问来源于stack exchange,提问作者DonSoft

