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

Flutter跳转NdefWritePage时Provider<Repository>找不到错误求助

解决Flutter中导航至NdefWritePage时的ProviderNotFoundException问题

问题描述

从首页导航至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?> 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 _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);
                }),
              ),
          ],
        ),
      ),
    );
  }
}

解决方案

问题根源

  1. _Home组件的Context错误覆盖:在_Home的build方法中,你将传入的context替换成了类成员的context,这个成员context是从App的build方法传递过来的,而App的build方法中的context并不包含MultiProvider提供的Repository(因为MultiProvider是App的父级)。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 03:24:53