skip to Main Content

Components that are using the Notifier are not getting updated, when the child components are changing the state

state_provider.dart

class StateProvider extends ChangeNotifier {
  // user data
  String _name = '';
  String _phoneNumber = '';
  String _email = '';
  String? _gender;
  String _locationSerachText = '';
  Map<String, dynamic> _viewPort = {};
  final List<String> _tags = [];
  List<LocationCard> _locationCards = [];

  // Getters for user data
  String get name => _name;
  String get phoneNumber => _phoneNumber;
  String get email => _email;
  String? get gender => _gender;
  Map<String, dynamic> get viewPort => _viewPort;
  String get locationSerachText => _locationSerachText;
  List<String> get tags => _tags;
  List<LocationCard> get locationCards => _locationCards;

  // Setters for user data
  set name(String value) {
    _name = value;
    notifyListeners();
  }

  set viewPort(Map<String, dynamic> value) {
    _viewPort = value;
    notifyListeners();
  }

  set locationSerachText(String value) {
    _locationSerachText = value;
    notifyListeners();
  }

  set locationCards(List<LocationCard> value) {
    _locationCards = value;
    notifyListeners();
  }

  set phoneNumber(String value) {
    _phoneNumber = value;
    notifyListeners();
  }

  set email(String value) {
    _email = value;
    notifyListeners();
  }

  set gender(String? value) {
    _gender = value;
    notifyListeners();
  }

  // Method to add tag
  void addTag(String tag) {
    _tags.add(tag);
    notifyListeners();
  }

  // Method to remove tag
  void removeTag(String tag) {
    _tags.remove(tag);
    notifyListeners();
  }

}

powerup_profile_screen.dart

class PowerUpProfileScreen extends StatefulWidget {
  @override
  _PowerUpProfileScreenState createState() => _PowerUpProfileScreenState();
}

class _PowerUpProfileScreenState extends State<PowerUpProfileScreen> {
  @override
  Widget build(
    BuildContext context,
  ) {
    final stateProvider = Provider.of<StateProvider>(context, listen: true);

    bool isAtLeastOneTagSelected() {
      return stateProvider.tags.isNotEmpty;
    }

    Color getFinishButtonColor() {
      return isAtLeastOneTagSelected() ? Colors.blue : Colors.grey;
    }

    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: SvgPicture.asset('assets/icons/back-arrow.svg'),
          onPressed: () {
            Navigator.of(context).pop();
          },
        ),
        actions: [
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 100.0),
            child: buildDotsDecorator(
              2,
              List.generate(3, (index) => 'Page $index'),
            ),
          )
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 5.0),
        child: Container(
          color: GuideroTheme.darkTheme.primaryColor,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const CustomHeaderText(text: 'Power up your profile'),
              const SizedBox(
                height: 12,
              ),
              const ParagrapghText(
                text: 'Choose tags that match your expertise and interests',
                fontSize: 16,
                textAlign: TextAlign.left,
              ),
              const SizedBox(
                height: 16.0,
              ),
              const ParagrapghText(
                text: 'You can select more than one tag',
                fontSize: 16,
                textAlign: TextAlign.left,
              ),
              const SizedBox(
                height: 32.0,
              ),
              buildTagCards([
                'Cafes & Pubs',
                'Resorts',
                'Hotels',
                'Social Events',
                'Tourist Spots'
              ]),
              const Spacer(),
              Padding(
                padding: const EdgeInsets.only(bottom: 8.0),
                child: CustomTextButton(
                  text: 'Finish',
                  onPressed: isAtLeastOneTagSelected()
                      ? () {
                          print("finish clicked ");
                        }
                      : null,
                  backgroundColor: getFinishButtonColor(),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget buildTagCards(List<String> tagTexts) {
    return Wrap(
      spacing: 30.0,
      runSpacing: 16.0,
      alignment: WrapAlignment.start,
      children: tagTexts.map((tagText) {
        return TagCard(
          tagText: tagText,
        );
      }).toList(),
    );
  }
}

Inside the TagCard widget, tags are added and removed 👇🏽

class TagCard extends StatefulWidget {
  final String tagText;

  TagCard({Key? key, required this.tagText}) : super(key: key);

  @override
  State<TagCard> createState() => _TagCardState();
}

class _TagCardState extends State<TagCard> {
  bool isSelected = false;

  @override
  Widget build(BuildContext context) {
    final stateProvider = Provider.of<StateProvider>(context);

    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(99),
        border: Border.all(
            color: isSelected ? Colors.white : Colors.white.withOpacity(0.5),
            width: 1),
        color: Colors.transparent,
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          GestureDetector(
            onTap: () {
              isSelected = stateProvider.tags.any(
                  (tag) => tag.toLowerCase() == widget.tagText.toLowerCase());
              print('Tag tapped: ${widget.tagText}');
              print('Tag selected: $isSelected');
              if (isSelected) {
                stateProvider.tags.remove(widget.tagText);
              } else {
                stateProvider.tags.add(widget.tagText);
              }
              setState(() {
                isSelected = !isSelected;
              });
              print("other tags ${stateProvider.viewPort}");
              print('Selcted tags : ${stateProvider.tags}');
            },
            child: Text(
              widget.tagText,
              style: const TextStyle(
                  color: Colors.white,
                  fontSize: 16.0,
                  fontWeight: FontWeight.w500),
            ),
          ),
        ],
      ),
    );
  }
}

Button colour is changed accordingly 👇🏽

final stateProvider = Provider.of<StateProvider>(context, listen: true);

bool isAtLeastOneTagSelected() {
   return stateProvider.tags.isNotEmpty;
}

Color getFinishButtonColor() {
   return isAtLeastOneTagSelected() ? Colors.blue : Colors.grey;
}

When the tags are selected/removed the state is not reflected in the parent component.
How to resolve this 💁🏽‍♂️

Tried getting the value directly

backgroundColor: stateProvider.tags.isNotEmpty ? Colors.blue : Colors.grey;

2

Answers


  1. You are mixin to state-management techniques, the internal state of stateful widget TagCard and the state of your change notifier, and their are not aligned. Your tag card isSelected is not defined by the change notifier but with the internal state of the StatefulWidget.

    I would simplify your TagCard widget and do as with many other widgets with a similar behavior in Flutter (CheckBox, Switch, Radio, …), make it such that it does not maintain any state. Instead, when the state of TagCard needs to change, the widget calls the [onSelect] or [onDeselect] callbacks and widgets that use a tag card will listen for the callbacks, update the state accordingly and rebuild the tag card with new values to update the visual appearance of the tag cards.

    class TagCard extends StatelessWidget {
      final String tagText;
    
      final bool isSelected;
    
      final VoidCallback onSelect;
      final VoidCallback onDeselect;
    
      TagCard({Key? key, required this.tagText,required this.isSelected,  required this.onSelect, required this.onDeselect,}) : super(key: key);
    
    
      @override
      Widget build(BuildContext context) {
    
        return Container(
          ...
              GestureDetector(
                onTap: () {
                  isSelected ? onDeselect() : onSelect();
                },
                child: Text(
                  tagText,
              ...
      }
    }
    
    Widget buildTagCards(final stateProvider, List<String> tagTexts) {
      return Wrap(
        spacing: 30.0,
        runSpacing: 16.0,
        alignment: WrapAlignment.start,
        children: tagTexts.map((tagText) {
          return TagCard(
            tagText: tagText,
            isSelected: stateProvider.tags.contains(tagText),
            onSelect: () => stateProvider.tags.add(tagText),
            onDeselect: () => stateProvider.tags.remove(tagText),
          );
        }).toList(),
      );
    }
    
    Login or Signup to reply.
  2.         You are wrong using Provider.
            
            The best practice for changing a state and seeing changes in the screen in real-time in provider state management is you must not do this 
            final stateProvider = Provider.of<StateProvider>(context, listen: true);
            
            and 
            
            setState((){});
            
            you must use the Consumer widget this widget provides you with an instance of your Provider and you can access methods that are implemented in your Provider.
            and after calling any methods like addTag() you can see changes on the screen in real time.
        
        
        class TagCard extends StatefulWidget {
          final String tagText;
        
          TagCard({Key? key, required this.tagText}) : super(key: key);
        
          @override
          State<TagCard> createState() => _TagCardState();
        }
        
        class _TagCardState extends State<TagCard> {
          bool isSelected = false;
        
          @override
          Widget build(BuildContext context) {    
            return Container(
              padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(99),
                border: Border.all(
                    color: isSelected ? Colors.white : Colors.white.withOpacity(0.5),
                    width: 1),
                color: Colors.transparent,
              ),
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                       Consumer<StateProvider>(
                builder:(context,stateProvider,child ){
                return GestureDetector(
                    onTap: () {
                      isSelected = stateProvider.tags.any(
                          (tag) => tag.toLowerCase() == widget.tagText.toLowerCase());
                      print('Tag tapped: ${widget.tagText}');
                      print('Tag selected: $isSelected');
                      if (isSelected) {
                        stateProvider.addTag(widget.tagText);
                      } else {
                        stateProvider.removeTag(widget.tagText);
                      }
                      print("other tags ${stateProvider.viewPort}");
                      print('Selcted tags : ${stateProvider.tags}');
                    },
                    child: Text(
                      widget.tagText,
                      style: const TextStyle(
                          color: Colors.white,
                          fontSize: 16.0,
                          fontWeight: FontWeight.w500),
                    ),
                  );
                }
                
                )
                ],
              ),
            );
          }
        }
        
    

    and if you don’t want to use consumer you can:

        context.read<StateProvider>().addTag(tag);    
    

    to see more link

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search