Search code examples
flutterflutter-layoutflutter-provider

Flutter: Getting Error With Provider Consumer on FloatingActionButton


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

Solution

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