【问题标题】:Could not find the correct provider above this widget在此小部件上方找不到正确的提供程序
【发布时间】:2019-11-29 03:28:44
【问题描述】:

我在使用 Flutter Provider 时遇到问题... 我的流程是这样的:登录用户 ID 传递给新的小部件后 -> 从那里它预先保存到数据库,然后重定向到新的小部件(仪表板)。

这是登录后小部件的代码:

return MaterialApp(
      title: title,
      home: Scaffold(
          appBar: AppBar(
            title: Text(title),
          ),
          body: ListView(
            children: <Widget>[
              Container(
                margin: EdgeInsets.all(8.0),
                child: Card(
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.all(Radius.circular(8.0))),
                  child: InkWell(
                    onTap: () {
                      var user = Provider.of<UserRepository>(context);
                      user.savePreference(user.user.id, "Something");
                      user.navigateToNewPage(Dashboard(), context);
                      print(user.user.id);
                    },

这行得通:

user.savePreference(user.user.id, "Something");

但这导致了一个问题:

user.navigateToNewPage(Dashboard(), context);

在仪表板小部件中,我正在创建这个:

 Widget build(BuildContext context) {
    var user = Provider.of<UserRepository>(context);

在 UserRepository 我有这个:

class UserRepository with ChangeNotifier {
  User user;
  Status _status = Status.Uninitialized;

  Status get status => _status;
  User get getUser => user;

  UserRepository.instance();

Future<void> navigateToNewPage(Widget page, BuildContext context) {
    Navigator.push(context, MaterialPageRoute(builder: (context) => page));
  }

我知道这个话题已经解决了问题,但是找不到适合我的问题的东西。

【问题讨论】:

    标签: flutter dart flutter-provider


    【解决方案1】:

    提供者范围

    MaterialApp
     > provider(Screen A)
     > Screen B
    

    如果 Provider 在屏幕 A 中被实例化,则在屏幕 B 中从 A → B 的 Navigator.push 之后将无法访问它。

    为什么?

    因为ProviderInheritedWidget,而Navigator 在其Screen A context 范围之外使用MaterialApp context。 (请参阅下面的详细信息。)

    修复

    Provider 移动到共同父级MaterialApp context,允许屏幕A 和B 都继承其状态/上下文。

    provider(MaterialApp)
     > Screen A
     > Screen B
    

    示例

    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        /// wrap MaterialApp in Provider widget
        return ChangeNotifierProvider(
          create: (context) => ColorModel(), // ← create/init your state model
          child: MaterialApp(
              home: ScreenA()
          ),
        );
      }
    }
    

    详情

    提供者

    • Provider 基于InheritedWidget。只有小部件可以继承父小部件的状态。

      • Provider 必须是任何想要访问“提供”状态对象的小部件树的根小部件

    导航器

    • 屏幕 A 上的 Navigator.push(context)不使用屏幕 A 中的 context
      • 它使用来自MaterialAppcontext
    • Navigator.push(context)实际上是Navigator.of(context).push
    • Navigator.of(context) 表示:向上搜索此上下文层次结构,直到找到实例化 Navigator 的上下文
      • 默认NavigatorMaterialApp 中实例化。
      • 除非您明确创建/使用不同的Navigator,否则您将使用默认值。
      • NavigatorcontextMaterialApp 的。
    • 屏幕 B 将获得 那个 上下文(MaterialApp),不是屏幕 A 的上下文。
      • B 是 A 的兄弟姐妹,而不是其孩子。
      • B 不会从 A 继承,即使它在 context A 中显示为“实例化”。
      • 屏幕 B 是MaterialApp context,而不是屏幕 A context 的子。
      • Provider context 范围,如果在屏幕 A 中定义,则不涵盖屏幕 B

    画面 A → B

    Navigator.push(context, MaterialPageRoute(builder: (context) => ScreenB()))
    

    其实是:

    Navigator.of(context).push(MaterialPageRoute(builder: (context) => ScreenB()))
    

    就像:

    Navigator.of(MaterialApp).push(
      MaterialPageRoute(builder: (MaterialAppContext) => ScreenB())
    )
    

    所以屏幕 B 在 MaterialApp context 下,不是 在屏幕 A context 下,因此无法访问屏幕 A Provider 及其 context

    关于Provider的类似问题请参见this answer for a code sample

    【讨论】:

    • 如何添加多提供者?
    • /// 在 Provider 小部件中包装 MaterialApp 对我有用 @AliYarKhan 我在下面评论了 multiprovider。
    • 在那种情况下,所有提供者都需要放入根小部件吗?
    • 我找不到一个有效的公共父亲小部件除了根小部件,这很奇怪
    【解决方案2】:

    Provider.of(context) 的参数context 必须是您定义的提供者的子级。

    @override
      Widget build(BuildContext context) {
        // !important here, Scaffold.of(context) returns null
        return Scaffold(
          appBar: AppBar(title: Text('Demo')),
          body: Builder(
            builder: (BuildContext context) {
              return FlatButton(
                child: Text('BUTTON'),
                onPressed: () {
                  // here, Scaffold.of(context) returns the locally created Scaffold
                  Scaffold.of(context).showSnackBar(SnackBar(
                    content: Text('Hello.')
                  ));
                }
              );
            }
          )
        );
      }
    

    官方样本:https://api.flutter.dev/flutter/widgets/BuildContext-class.html

    这是我的代码:

      @override
      Widget build(BuildContext context) {
        return MultiProvider(
          providers: [ChangeNotifierProvider<SomeModel>(
            create: (context){
              return SomeModel();
            },
          ),],
          child: Builder(builder: (BuildContext context){
            BuildContext rootContext = context;
            return Container(
    //Here to use rootContext is safe
    //Provider.of<SomeModel>(rootContext, listen: false);
            );
          }),
        );
    }
    

    【讨论】:

      【解决方案3】:

      尝试将位于 Scaffold 下方的小部件树的一部分提取到单独的小部件中。 您现在使用的上下文用于构建尚未导航器的顶级小部件。

      生成的代码应如下所示:

      return MaterialApp(
            title: title,
            home: Scaffold(
                appBar: AppBar(
                  title: Text(title),
                ),
                body: LoginWidget()
      
      class LoginWidget extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return ListView(
                  children: <Widget>[
                    Container(
                      margin: EdgeInsets.all(8.0),
                      child: Card(
                        shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.all(Radius.circular(8.0))),
                        child: InkWell(
                          onTap: () {
                            var user = Provider.of<UserRepository>(context);
                            user.savePreference(user.user.id, "Something");
                            user.navigateToNewPage(Dashboard(), context);
                            print(user.user.id);
                          },
      
      ...
      
        }
      }
      

      【讨论】:

      • 您能说得更具体些吗?
      【解决方案4】:

      这就是我们与多提供商一起使用的方式

      class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MultiProvider(
          providers: [
            ChangeNotifierProvider(
              create: (context) => GeneralProvider(),
            ),
          ],
          child: MaterialApp(
            home: InitPage(),
            onGenerateRoute: MyRouter.generateRoute,
            initialRoute: '/InitPage',
            debugShowCheckedModeBanner: false,
            title: 'Eray',
          ),
        );
      }
      }
      

      【讨论】:

        【解决方案5】:

        可能会在DetailScreen 中回答。

        我不知道你是否解决了这个问题。但我只是使用一个参数解决了。 首先我的问题是,我有一个list screen、一个detail screen 和一个listBloc。我在list screen 中提供了listBloc。像这样:

        class ListScreen extends StatelessWidget {
          static const String routeName = '/list_screen';
        
          ListScreen({Key key}) : super(key: key);
        
          static Route route(Map<String, dynamic> args) {
            return MaterialPageRoute(
              builder: (_) => ListScreen(),
              settings: const RouteSettings(name: routeName),
            );
          }
        
        
          @override
          Widget build(BuildContext context) {
            return MultiBlocProvider(
                providers: [
                  BlocProvider(
                    create: (context) =>ListBloc(),
                  )
                ]
                child: DetailScreen(),
            );
          }
        }
        

        而且,我需要在detail screenBlocBuilder 中使用listBloc

        class DetailScreen extends StatelessWidget {
        
          // you need a context
          final BuildContext context;
        
          // get context
          const DetailScreen({
            Key key,
            @required this.context,
          }) : super(key: key);
          
           @override
          Widget build(BuildContext context) {
            // get bloc using your context
            final bloc = this.context.read<ListBloc>();
            return BlocBuilder<ListBloc, ListState>(
              // provide your bloc
              bloc: bloc,
              builder: (context, state) {}
            );
          }
        }
        

        【讨论】:

          【解决方案6】:

          我从错误的路径导入了我的 Provider:

          import 'file:///D:/Projects/Flutter/library_app/lib/MyStateManagement/local/temp_management.dart';
          

          当我更改路径的导入时,它对我的​​工作:

          import 'package:library_app/MyStateManagement/local/temp_management.dart';
          

          当我更改提供程序文件的文件夹时,这个问题发生在我身上。

          【讨论】:

            猜你喜欢
            • 2021-11-18
            • 2021-01-13
            • 2021-07-22
            • 2021-11-11
            • 2020-12-01
            • 2020-08-04
            • 2020-09-18
            • 2020-07-26
            • 2021-07-04
            相关资源
            最近更新 更多