0

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(),
        },
      ),
    );
  }
}
Asad Sheikh
  • 61
  • 1
  • 8

2 Answers2

1

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

Md. Yeasin Sheikh
  • 54,221
  • 7
  • 29
  • 56
  • No. Let me rephrase it. I have another class `Courses` which is in `courses.dart` taking care of all the `Course` models present in `course.dart`. So the issue is not the spelling I guess. It is something inside the builder method on FloatingActionButton inside the `category_course_screen.dart` Edit: I have also added the `courses.dart` file. – Asad Sheikh Jul 31 '22 at 19:33
  • You like to update specific course from `CategoryCourseScreen`? – Md. Yeasin Sheikh Jul 31 '22 at 19:36
  • Yes, on the basis of `course.id` the course will be marked as a favourite on pressing the `FloatingActionButton`. – Asad Sheikh Jul 31 '22 at 19:42
  • OK check the update one. While you are getting id it will solve your case. – Md. Yeasin Sheikh Jul 31 '22 at 19:51
  • thanks it solves the problem. But can you explain to me how can you do that by just adding a copyWith method and replacing the `toggleFavourite()` to `Courses` class? – Asad Sheikh Jul 31 '22 at 20:04
  • if we use it on `Course` means it will affect the `Couse` not the course list. And copyWith is a factory constructor, it clone the object & if you want to change a single field, just call the parameter. if you don't provide a filed, it will take current value. like `isFavourite: isFavourite ?? this.isFavourite,` Here is a good [question](https://stackoverflow.com/q/62372580/10157127) and [this](https://stackoverflow.com/q/60352077/10157127) – Md. Yeasin Sheikh Jul 31 '22 at 20:07
0

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());
  }
}
HKN
  • 264
  • 1
  • 8