在此小部件上方找不到正确的提供程序

时间:2019-07-20 11:25:17

标签: flutter dart flutter-provider

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

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

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));
  }

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

4 个答案:

答案 0 :(得分:32)

提供者范围

MaterialApp
 > provider(Screen A)
 > Screen B

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

为什么?

因为 ProviderInheritedWidget 并且 Navigator 在其 Screen A MaterialApp context 范围之外使用 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
    • 它使用来自 contextMaterialApp
  • Navigator.push(context) 实际上是 Navigator.of(context).push
  • Navigator.of(context) 表示:向上搜索这个上下文层次结构,直到找到一个实例化导航器的上下文
    • 默认 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

答案 1 :(得分:1)

尝试将位于脚手架下方的小部件树的一部分提取到单独的小部件。 您现在使用的上下文用于构建您的顶级窗口小部件,而该窗口小部件还没有Navigator。

生成的代码应如下所示:

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);
                    },

...

  }
}

答案 2 :(得分:1)

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 :(得分:0)

也许在DetailScreen回答。

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

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(),
    );
  }
}

而且,我需要在 listBlocdetail screen 中使用 BlocBuilder

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) {}
    );
  }
}