So I'm new to Flutter learning about the Provider package. I want to toggle the bool isFavourite
and the Icon
when the FloatingActionButton
is pressed. But with using Provider, Consumer I'm getting an error saying:
Error: Could not find the correct Provider above this Consumer 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 Consumer is under your MultiProvider/Provider. This usually happens when you are creating a provider and trying to read it immediately.
Below is my code for course.dart
which is Provider:
import 'package:flutter/material.dart';
class Course with ChangeNotifier {
final String id;
final String title;
final String description;
final double rating;
final String imageUrl;
bool isFavourite;
final bool isFeatured;
Course({
required this.id,
required this.title,
required this.description,
required this.rating,
required this.imageUrl,
this.isFavourite = false,
required this.isFeatured,
});
void toggleFavouriteStatus() {
isFavourite = !isFavourite;
notifyListeners();
}
}
Below is my code for courses.dart
:
import 'package:flutter/material.dart';
import 'course.dart';
class Courses with ChangeNotifier {
final List<Course> _items = [
Course(
id: 'c1',
title: 'HTML & CSS',
description: 'This is the description of HTML & CSS',
rating: 4.5,
imageUrl: 'http://placeimg.com/640/480/tech',
isFeatured: true,
),
Course(
id: 'c2',
title: 'JAVASCRIPT',
description: 'This is the description of JAVASCRIPT',
rating: 4.9,
imageUrl: 'http://placeimg.com/1000/800/tech',
isFeatured: true,
),
Course(
id: 'c3',
title: 'WEB DEVELOPMENT',
description: 'This is the description of WEB DEVELOPMENT',
rating: 5,
imageUrl: 'http://placeimg.com/640/480/tech',
isFeatured: false,
),
Course(
id: 'c4',
title: 'APP DEVELOPMENT',
description: 'This is the description of APP DEVELOPMENT',
rating: 3.8,
imageUrl: 'http://placeimg.com/1000/800/tech',
isFeatured: false,
),
Course(
id: 'c5',
title: 'MACHINE LEARNING',
description: 'This is the description of MACHINE LEARNING',
rating: 5,
imageUrl: 'http://placeimg.com/640/480/tech',
isFeatured: false,
),
];
List<Course> get items {
return [..._items];
}
List<Course> get favouriteItems {
return _items.where((course) => course.isFavourite).toList();
}
Course findById(String id) {
return items.firstWhere((course) => course.id == id);
}
}
Below is my code for category_course_screen.dart
which is Consumer:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '/providers/course.dart';
class CategoryCourseScreen extends StatelessWidget {
CategoryCourseScreen({Key? key}) : super(key: key);
static const routeName = '/category-course-screen';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Title'),
),
floatingActionButton: Consumer<Course>(
builder: (_, course, child) => FloatingActionButton(
child: Icon(
course.isFavourite ? Icons.favorite : Icons.favorite_border,
),
onPressed: () {
course.toggleFavouriteStatus();
},
),
),
);
}
}
Below is my code for main.dart
file:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '/providers/courses.dart';
import '/screens/tabs_screen.dart';
import '/screens/enrolled_screen.dart';
import '/screens/favourites_screen.dart';
import '/screens/profile_screen.dart';
import '/screens/category_course_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => Courses(),
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Course App',
theme: ThemeData(
fontFamily: 'Lato',
primarySwatch: Colors.cyan,
accentColor: Colors.white,
textTheme: ThemeData.light().textTheme.copyWith(
subtitle1: const TextStyle(
fontSize: 30,
fontFamily: 'Lato-Bold',
fontWeight: FontWeight.w900,
),
bodyText1: const TextStyle(
fontSize: 20,
fontFamily: 'Lato-Thin',
),
bodyText2: const TextStyle(
fontSize: 20,
fontFamily: 'Lato-Bold',
color: Colors.white,
fontWeight: FontWeight.w900,
),
),
),
// home: HomeScreen(),
initialRoute: '/',
routes: {
'/': (context) => TabsScreen(),
EnrolledScreen.routeName: (context) => EnrolledScreen(),
FavouritesScreen.routeName: (context) => FavouritesScreen(),
ProfileScreen.routeName: (context) => ProfileScreen(),
CategoryCourseScreen.routeName: (context) => CategoryCourseScreen(),
},
),
);
}
}
CodePudding user response:
I think the problem is that the provider you are trying to read is in different route. So instead of wrapping your whole MaterialApp with MultiProvider you would do something like this:
class CoursesPage extends StatelessWidget {
const CoursesPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => Courses(),
),
],
child: CategoryCourseScreen());
}
}
CodePudding user response:
I would say you dont need to notify the Course class, else create a simple class like this with copyWith constructor for more benifit.
class Course {
final String id;
final String title;
final String description;
final double rating;
final String imageUrl;
bool isFavourite;
final bool isFeatured;
Course({
required this.id,
required this.title,
required this.description,
required this.rating,
required this.imageUrl,
this.isFavourite = false,
required this.isFeatured,
});
Course copyWith({
String? id,
String? title,
String? description,
double? rating,
String? imageUrl,
bool? isFavourite,
bool? isFeatured,
}) {
return Course(
id: id ?? this.id,
title: title ?? this.title,
description: description ?? this.description,
rating: rating ?? this.rating,
imageUrl: imageUrl ?? this.imageUrl,
isFavourite: isFavourite ?? this.isFavourite,
isFeatured: isFeatured ?? this.isFeatured,
);
}
}
Now Courses
class will have an extra method to change the favorite option
void toggleFavorite(String id) {
final item = findById(id);
_items.remove(item);
_items.add(item.copyWith(isFavourite: !item.isFavourite));
notifyListeners();
}
And update sample cases
class CategoryCourseScreen extends StatelessWidget {
const CategoryCourseScreen({Key? key}) : super(key: key);
static const routeName = '/category-course-screen';
@override
Widget build(BuildContext context) {
final id = 'c1'; //example
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
title: Text('Title'),
),
floatingActionButton: Consumer<Courses>(
builder: (_, courses, child) => FloatingActionButton(
backgroundColor:
courses.findById(id).isFavourite ? Colors.pink : Colors.amber,
onPressed: () {
courses.toggleFavorite(id);
},
),
),
);
}
}