Error: Could not find the correct Provider<TodoModel> above this Builder Widget

Error: Could not find the correct Provider<TodoModel> above this Builder Widget

我正在开发一个微型 to-do 列表应用程序,我是 Provider 包的新手。我收到此错误:

Error: Could not find the correct Provider above this Builder Widget. This happens because you used a 'BuildContext' that does not include the provider of your choice.

When the exception was thrown, this was the stack: 
#0      Provider._inheritedElementOf (package:provider/src/provider.dart:332:7)
#1      Provider.of (package:provider/src/provider.dart:284:30)
#2      HomePage.build.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:mytodolist/main.dart:57:36)
#3      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:994:20)
#4      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
...
Handler: "onTap"
Recognizer: TapGestureRecognizer#9540a
  debugOwner: GestureDetector
  state: possible
  won arena
  finalPosition: Offset(308.6, 425.5)
  finalLocalPosition: Offset(34.8, 20.3)
  button: 1
  sent tap down

这是我的 main.dart:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:mytodolist/TodoModel.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          brightness: Brightness.light,
          primaryColor: Colors.blue,
          accentColor: Colors.orange,
        ),
        home: ChangeNotifierProvider(
          create: (context) => TodoModel(),
          child: HomePage(),
        )
    );
  }
}


class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("mytodos"),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          showDialog(
              context: context,
              builder: (BuildContext context) {
                return AlertDialog(
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(8)),
                  title: Text("Add Todolist"),
                  content: TextField(
                    onChanged: (String value) {
                      Provider.of<TodoModel>(context, listen: false)
                          .addTitle(value);
                    },
                  ),
                  actions: <Widget>[
                    TextButton(
                        onPressed: () {
                          Provider.of<TodoModel>(context, listen: false)
                              .createTodos();
                          Navigator.of(context).pop();
                        },
                        child: Text("Add"))
                  ],
                );
              });
        },
        child: Icon(
          Icons.add,
          color: Colors.white,
        ),
      ),
      body: StreamBuilder(
          stream: FirebaseFirestore.instance.collection("MyTodos").snapshots(),
          builder: (context, snapshots) {
            if (snapshots.data == null) return CircularProgressIndicator();
            return Consumer<TodoModel>(builder: (context, todo, child) {
              return ListView.builder(
                  shrinkWrap: true,
                  itemCount: snapshots.data.docs.length,
                  itemBuilder: (context, index) {
                    DocumentSnapshot documentSnapshot =
                        snapshots.data.docs[index];
                    return Dismissible(
                        onDismissed: (direction) {
                          Provider.of<TodoModel>(context, listen: false)
                              .deleteTodos(documentSnapshot["todoTitle"]);
                        },
                        key: Key(documentSnapshot["todoTitle"]),
                        child: Card(
                          elevation: 4,
                          margin: EdgeInsets.all(8),
                          shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(8)),
                          child: ListTile(
                            title: Text(documentSnapshot["todoTitle"]),
                            trailing: IconButton(
                              icon: Icon(Icons.delete),
                              color: Colors.red,
                              onPressed: () {
                                /*setState(() {
                                todos.removeAt(index);
                              });*/
                                Provider.of<TodoModel>(context, listen: false)
                                    .deleteTodos(documentSnapshot["todoTitle"]);
                              },
                            ),
                          ),
                        ));
                  });
            });
          }),
    );
  }
}

这是我的 TodoModel.dart:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';

class TodoModel extends ChangeNotifier {
  // List todos = [];
  String todoTitle = "";

  createTodos() {
    DocumentReference documentReference =
    FirebaseFirestore.instance.collection("MyTodos").doc(todoTitle);
    // Map
    Map<String, String> todos = {"todoTitle": todoTitle};
    documentReference.set(todos).whenComplete(() =>
        print("$todoTitle created"));
  }

  deleteTodos(item) {
    DocumentReference documentReference =
    FirebaseFirestore.instance.collection("MyTodos").doc(item);
    documentReference.delete().whenComplete(() => print("$item deleted"));
  }

  addTitle(String title) {
    todoTitle = title;
  }
}

main.dart 中,我相信我已经将 ChangeNotifierProvider 提升到足以使其 child、HomePage class,使用 [=18] =].为什么在Builder Widget上面还提示找不到正确的Provider?

请使用 HomePage 的上下文而不是 showDialog 的构建器。

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {  // <<<<<<< USE THIS CONTEXT
    return Scaffold(
      appBar: AppBar(
        title: Text("mytodos"),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          showDialog(
              context: context,
              builder: (BuildContext dialogContext) {  // <<<<<<< DO NOT USE THIS CONTEXT
                return AlertDialog(
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(8)),
                  title: Text("Add Todolist"),
                  content: TextField(
                    onChanged: (String value) {
                      // <<<<<<< context = HomePage's BuildContext
                      // <<<<<<< context != dialogContext
                      Provider.of<TodoModel>(context, listen: false)
                          .addTitle(value);
                    },
                  ),
                  actions: <Widget>[
                    TextButton(
                        onPressed: () {
                          // <<<<<<< context = HomePage's BuildContext
                          // <<<<<<< context != dialogContext
                          Provider.of<TodoModel>(context, listen: false)
                              .createTodos();
                          Navigator.of(context).pop();
                        },
                        child: Text("Add"))
                  ],
                );
              });
        },

当您使用 showDialog() 方法时,内部 BuildContext 和外部 BuildContext 可能会纠缠在一起,这意味着您的 UI 可能会出现不当行为。为了解决问题,您应该在 build() 的开头声明您的公共 class,然后在 AlertDialog 中重用它(这也是重用代码的最佳实践):

class HomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    final todoModel = Provider.of<TodoModel>(context, listen: false);
    return Scaffold(
      appBar: AppBar(
        title: Text("mytodos"),
      ),
      ...

// Then use it in the dialog

showDialog(
    context: context,

    ...

    content: TextField(
    onChanged: (String value) {
          todoModel.addTitle(value); // Use it here
      },
    ),

您还可以通过其他方式解决此问题。您可以创建一个单独的小部件来容纳对话框的内容,尤其是当 UI 与 AlertDialog 不相似时,这迫使我们创建一个自定义 UI 小部件:

// This is useful when you cannot use AlertDialog's UI, but I'll put it here for the example
class CustomDialog extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
      title: Text("Add Todolist"),
      content: TextField(
        onChanged: (String value) {
          Provider.of<TodoModel>(context, listen: false).addTitle(value);
        },
      ),
      actions: <Widget>[
        TextButton(
            onPressed: () {
              Provider.of<TodoModel>(context, listen: false).createTodos();
              Navigator.of(context).pop();
            },
            child: Text("Add"))
      ],
    );
  }
}

最后,在向小部件树提供通用 classes 时,有时您可能 运行 遇到导航到其他屏幕的问题并收到 Provider not found 错误。

Providersscoped 因此如果它位于小部件树内,则只有它的后代才能访问它。你应该把它移到上面 MaterialApp:

@override
  Widget build(BuildContext context) {
      return ChangeNotifierProvider(
           create: (context) => TodoModel(),
           child: MaterialApp(
               debugShowCheckedModeBanner: false,
               theme: ThemeData(
               ...