1
import 'package:flutter/material.dart';
import '../../Data/Globalvariable.dart' as global;

class MenuBar extends StatefulWidget {
  final key = UniqueKey();
  // final pin;
  // MenuBar({this.pin='112'});
  @override
  _MenuBarState createState() {
    return _MenuBarState();
  }
}

class _MenuBarState extends State<MenuBar> {
  ValueNotifier<List<String>> st = ValueNotifier(global.pincode);
  Widget total() {
    return ListView.builder(
      itemCount: global.pincode.length,
      itemBuilder: (context, index) {
        final item = global.pincode[index];
        return Dismissible(
          key: UniqueKey(),
          onDismissed: (direction) {
            setState(() {
              global.pincode.removeAt(index);
            });
            ScaffoldMessenger.of(context)
                .showSnackBar(SnackBar(content: Text('$item Removed')));
          },
          background: Container(color: Colors.red),
          child: ListTile(
            title: Text(item),
          ),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder(
      builder: (context, n, menu) {
        print(global.pincode);
        return total();
      },
      child: total(),
      valueListenable: st,
    );
  }
}

this is my main file i have a global file and all of my working variables are there

Globalvariable.dart

library project.globals;
List<String> pincode = ['Select Pin','110084','110088'];

now as i am adding data into pincode valuenotifier should listen to it and update the widget automatically but it is not doing so i verified that using debug console it gets updated when i call setstate which is ok but i want to update widget as i add data to my list

enter image description here

this is to demonstrate what i said above that wigdet is updating on removal but not for addition

Nimish Khattar
  • 31
  • 1
  • 10
  • 1
    use st.notifyListeneres() instead of setState ! – Naveen Avidi Aug 17 '21 at 05:02
  • I think you have to use `st` somehow in `ValueListenableBuilder` `builder`, like `return total(st)`, otherwise it will return the same regardless of `st` value. – Peter Koltai Aug 17 '21 at 07:15
  • 2
    This is the third time I see this misconception, your `ValueNotifier>` will not know when the value is changed or not. See the source of `ValueNotifier`, it uses equality operator `==`. This won't work for lists, you are comparing the same List pointer with itself (even though List's content changed). See this answer on list comparison in Dart https://stackoverflow.com/a/22333042/10830091 – om-ha Nov 08 '21 at 23:58
  • First of all let's get this out of the way. You cannot update your list like that `global.pincode.removeAt(index);`. You have to `st` instead `st.value.removeAt(index);` – om-ha Nov 09 '21 at 00:01
  • 1
    After you have solved that issue. You have 2 solutions. A. forcefully tell ValueNotifier its value has changed `st.value = List.from(st.value)..removeAt(index);` B. subclass ValueNotifier and define methods for adding/deleting elements within the list, calling notifyListeners manually within said methods. – om-ha Nov 09 '21 at 00:18

3 Answers3

7

You don't need to use SetState if you already use ValueNotifier.
To update state, use: st.value = new value.

Because you already wrap your child widget with ValueListenableBuilder, the state will automatically change if value of st changes.


ValueNotifier uses equality to detect change. For List, the dart compiler won't notice any change if you only add/remove value of the list.

To overcome the problem, you need to create a new list so that the dart compiler detects the change.

You can update your code like this:

return Dismissible(
  key: UniqueKey(),
  onDismissed: (direction) {
    st.value = List.from(st.value)..removeAt(index);
    ScaffoldMessenger.of(context)
        .showSnackBar(SnackBar(content: Text('$item Removed')));
  },
  background: Container(color: Colors.red),
  child: ListTile(
    title: Text(item),
  ),
);
  • Understanding the identify function (i.e. hashcode/equals) of your objects is helpful here so the ValueNotifier can detect the change – Dana Apr 15 '23 at 21:32
  • Or you can create a `ValueNotifier` subclass and call `notifyListeners` yourself: https://stackoverflow.com/a/76378538 – Suragch Jun 01 '23 at 03:41
1

To make life easier, you can write a class that extends ValueNotifier<List> and define methods add and remove, as in the example below. and then just use _yourNotifier.add(newValue); So u don't need to repeat List.from(st.value)..removeAt(index) and so on.

class ValueNotifierList<T> extends ValueNotifier<List<T>> {
  ValueNotifierList(List<T> value) : super(value);

  void add(T valueToAdd) {
    value = [...value, valueToAdd];
  }

  void remove(T valueToRemove) {
    value = value.where((value) => value != valueToRemove).toList();
  }
}
0

If you're using ValueNotifier with a list you should append to that list st using st.value.add(), modifying the global.pincode list won't reflect unless you make that global list a ValueNotifier.

So you'll either have st.value.add() or in your global file ValueNotifier<List<String>> pincode = ValueNotifier(<String>[...]); with a valueListenable: global.pincode and modifications using global.pincode.value.xyz

faithomotoso
  • 385
  • 2
  • 10
  • This is a part of the solution yes, updates should be made via ValueNotifier object `st` and not the global list `global.pincode`. The second part of the solution concerns list equality and how `ValueNotifier` knows when it should notify its listeners. ValueNotifier sees the new value as different from the old value via equality i.e. `==` operator, then if there's a difference a notification occurs. With lists, that won't work because of how list equality works. Please see my comments above. – om-ha Nov 09 '21 at 00:05