Home > Software design >  Flutter: Getting Error With Provider Consumer on FloatingActionButton
Flutter: Getting Error With Provider Consumer on FloatingActionButton

Time:08-01

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

  • Related