How to solve ‘Tried to use Provider with a subtype of Listenable-Stream’ Provider error in Flutter

How to solve ‘Tried to use Provider with a subtype of Listenable/Stream’ Provider error in Flutter

Provider is one of the most popular package of Flutter and you may come across the error “Tried to use Provider with a subtype of Listenable/Stream” while using in an application.

The Provider package may also recommend you a solution like this –

This is likely a mistake, as Provider will not automatically update dependents
when DataProvider is updated. Instead, consider changing Provider for more specific
implementation that handles the update mechanism, such as:

– ListenableProvider
– ChangeNotifierProvider
– ValueListenableProvider
– StreamProvider

There are couple of ways to solve this error depending on how you want to use the package.

Solution 1:

This is according to the message suggested from the Provider package.

If you want to use the Provider to access some data and also wants to update the state of an widget whenever the state changes  then instead of using Provider in the main.dart file (usually the provider is injected/provided here) you can use any of the suggested provider such as ListenableProvider.

example:

in main.dart file

void main() {
  runApp(
      ListenableProvider(create: (context) => DataProvider(),child: MyApp(), )
  );
}

Instead of ListenableProvider, you can use any other suggested provider depending on your application requirement.

Solution 2:

If you want to use your provider only to access some data across the application which never changes throughout the application, then you can make your class (provider class) do not extend ChangeNotifier

example:

import 'package:flutter/material.dart';

class DataProvider {
  final String imgSource = 'https://example.com/assets/images';  
}

Now you can use Provider class inside the main.dart file to provide the provider.

How to Change - Update the State inside Bottom Sheet in Flutter

How to Change / Update the State inside Bottom Sheet in Flutter

Bottom Sheets are one of the commonly used things in a mobile application. We can use them for different types of requirements. If we want to display some information to the user such as more details about something, then it can be a good place.

If we want to keep some settings inside the Bottom Sheet such as a check box, then handling the state of this check box can be tricky.

example:

bool toggleIcon = true;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        child: Center(
          child: Column(
            children: [
              Icon(
                  toggleIcon ? Icons.check_box : Icons.check_box_outline_blank
              ),
              ElevatedButton(onPressed: () {
                showModalBottomSheet(
                    context: context,
                    builder: ( BuildContext context ) {
                      return Container(
                        color: Colors.blue,
                        child: Column(
                          children: [
                            IconButton(onPressed:  () {
                              setState(() {
                                toggleIcon= !toggleIcon;
                              });
                            } ,
                                icon: Icon(
                                    toggleIcon ? Icons.check_box : Icons.check_box_outline_blank
                                ) )
                          ],
                        ),
                      );
                    }
                );
              } , child: Text('Show Bottom Sheet'))
            ],
          ),
        ),
      ),
    ) ;
  }

In this example, we are showing bottom sheet which contains an icon button which changes the state of a property when we click on it. When we run this code and clicks on the icon button, then we can see that the icon which is inside the main body changes but not the icon which is inside the bottom sheet.

The reason is, when we click on the icon inside the bottom sheet, we are changing the state of the main widget but not the state of the bottom sheet. When we close the bottom bar and open it again, then we pass the updated context so we can find the updated icon.

Here, if you want, what you can do is, when the user clicks on the icon button inside the bottom sheet, after setting the state, you can immediately pop the bottom sheet by using Navigator.pop(context).

By Using StatefulBuilder

If you are concerned only about the state of the bottom sheet, and do not want to reflect any changes in the calling widget, you can use StatefulBuilder widget.

example:

replace the showModalBottomSheet from the previous example with this one

showModalBottomSheet(
                    context: context,
                    builder: ( BuildContext context ) {
                      return StatefulBuilder(
                        builder: (BuildContext context, StateSetter setState) {
                          return Container(
                            color: Colors.blue,
                            child: Column(
                              children: [
                                IconButton(onPressed:  () {
                                  setState(() {
                                    toggleIcon= !toggleIcon;
                                  });
                                } ,
                                    icon: Icon(
                                        toggleIcon ? Icons.check_box : Icons.check_box_outline_blank
                                    ) )
                              ],
                            ),
                          );
                        } ,
                      );
                    }
                )

With this code, we are giving the bottom sheet, its own state. When you run the code with this example, now when you click on the icon button inside bottom sheet, you can find that the icon toggles inside the bottom sheet, but the one inside the body stays same.

Bottom Sheet as Stateful Widget

In-order to change the state inside the bottom sheet as well as the widget from which it is called, we can make a stateful widget and use it as a bottom sheet. We also need to pass a callback function to this widget which triggers setState in the parent widget.

example:

import 'package:flutter/material.dart';
class BottomSheetState extends StatefulWidget {
  @override
  _BottomSheetStateState createState() => _BottomSheetStateState();
}

class _BottomSheetStateState extends State<BottomSheetState> {
  bool toggleIcon = true;
  toggleIconState(bool value ) {
    setState(() {
      toggleIcon = value;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        child: Center(
          child: Column(
            children: [
              Icon(
                  toggleIcon ? Icons.check_box : Icons.check_box_outline_blank
              ),
              ElevatedButton(onPressed: () {
                showModalBottomSheet(
                    context: context,
                    builder: ( BuildContext context ) {
                      return StatefulBottomSheet(toggleIcon: toggleIcon, valueChanged: toggleIconState,);
                    }
                );
              } , child: Text('Show Bottom Sheet'))
            ],
          ),
        ),
      ),
    ) ;
  }
}

class StatefulBottomSheet extends StatefulWidget {
  final bool toggleIcon;
  final ValueChanged<bool> valueChanged;
  StatefulBottomSheet( {Key? key, required this.toggleIcon, required this.valueChanged } );

  @override
  _StatefulBottomSheetState createState() => _StatefulBottomSheetState();
}

class _StatefulBottomSheetState extends State<StatefulBottomSheet> {
  late bool _toggleIcon;
  @override
  void initState() {
    _toggleIcon = widget.toggleIcon;
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    return  Container(
      color: Colors.blue,
      child: Column(
        children: [
          IconButton(onPressed:  () {
            setState(() {
              _toggleIcon= !_toggleIcon;
            });
            widget.valueChanged(_toggleIcon);
          } ,
              icon: Icon(
                  _toggleIcon ? Icons.check_box : Icons.check_box_outline_blank
              ) )
        ],
      ),
    );
  }
}

By using Provider

Another way to achieve this functionality is by using Provider package. We can now create a provider and move all the properties and logic to it and add it to the main.dart file.

import 'package:flutter/material.dart';

class DataProvider extends ChangeNotifier {
  bool toggleIcon = true;

  toggleIconState() {
    toggleIcon = !toggleIcon;
    notifyListeners();
  }
}

We can now use this provider to change the state inside the widget as well as in the bottom sheet.

@override
  Widget build(BuildContext context) {
    DataProvider dataProvider = Provider.of<DataProvider>(context);
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        child: Center(
          child: Column(
            children: [
              Icon(
                  dataProvider.toggleIcon ? Icons.check_box : Icons.check_box_outline_blank
              ),
              ElevatedButton(onPressed: () {
                showModalBottomSheet(
                    context: context,
                    builder: ( BuildContext context ) {
                      return Container(
                        color: Colors.blue,
                        child: Column(
                          children: [
                            IconButton(onPressed:  () {
                              Provider.of<DataProvider>(context, listen: false).toggleIconState();
                            } ,
                                icon: Icon(
                                    Provider.of<DataProvider>(context,).toggleIcon ? Icons.check_box :
                                    Icons.check_box_outline_blank
                                ) )
                          ],
                        ),
                      );
                    }
                );
              } , child: Text('Show Bottom Sheet'))
            ],
          ),
        ),
      ),
    ) ;
  }

Make sure that you have added the Provider dependency in the pubspec file.