I am developing a tiny to-do list app and I am new to the Provider
package. I get this error:
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
Here's my 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"]);
},
),
),
));
});
});
}),
);
}
}
And here's my 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;
}
}
In main.dart
, I believe I have lifted up the ChangeNotifierProvider
high enough to make its child, HomePage
class, use Provider.of<TodoModel>(content).xxx
. Why does it still popping up the error of not finding the correct Provider above the Builder Widget?
When you use showDialog()
method, the inner BuildContext
and outer BuildContext
can get tangled up which implies potential misbehaviors for your UI. To clear things out, you should declare your common class at the beginning of build()
, then reuse it within the AlertDialog
(which is also a best practice for reusing code):
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
},
),
There are other ways you can resolve the issue as well. You can create a separate widget that house the dialog's content, especially when the UI doesn't resemblance AlertDialog
, which force us to create a custom UI widget:
// 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"))
],
);
}
}
Finally, when providing common classes down to the widget tree, sometimes you can run into problems of navigating to other screens and receive the Provider not found
error.
Providers
are scoped so only its descendants can access it if it's located inside the widget tree. You should move it to above MaterialApp
:
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => TodoModel(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
...