Is it okay to use an onPressed function in a ChangeNotifier child class

105 Views Asked by At

I am using provider for state management, and I've given onTap a value in a function in the ChangeNotifier child class but my app is unresponsive, I mean, when i tap on the widget, it doesn't update state, however, it does change the values i need it to change tho, i know this coz I am debugPrinting in the onTap function and when i tap, it actually prints that the button got tapped, but state doesn't update, widget remains the same until i hot restart, then it updates everything, even hot reload doesn't update it, here's the function

class Storage extends ChangeNotifier{

  static const _storage =  FlutterSecureStorage();
  static const _listKey = 'progress';
  List _dataMaps = [];
  List<DayTile> dayTileMain = [];

  void createDataMap() {
    for (int i = 1; i < 101; i++) {
      final data = Data(number: i).toJson();
      _dataMaps.add(data);
    }
  }

  void createDayTiles() {
    for(Map<String, dynamic> data in _dataMaps) {
      bool isDone = data['i'];
      final dayTile = DayTile(
        number: data['n'],
        isDone: isDone,
        // This is where i need to rebuild the tree
        onTap: () async {
          data['i'] = true;
          notifyListeners();
          print(data['i']);
          print(isDone);
          await writeToStorage();
        },
      );
      dayTileMain.add(dayTile);
    }
    print('data tiles created');
  }
}

and here is the DayTile class

class DayTile extends StatelessWidget {
  const DayTile({
    Key? key,
    required this.number,
    required this.isDone,
    required this.onTap,
  }) : super(key: key);

  final int number;
  final VoidCallback onTap;
  final bool isDone;


  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        height: 50,
        width: MediaQuery.of(context).size.width * .15,
        decoration: BoxDecoration(
          color: !isDone
              ? const Color(0xffedecea)
              : const Color(0xffedecea).withOpacity(0.1),
          borderRadius: BorderRadius.circular(5),
        ),
        child: Center(
          child: Stack(
            alignment: Alignment.center,
            children: [
              Center(
                child: Text(
                  number.toString(),
                  style: const TextStyle(
                    color: Color(0xff576aa4),
                  ),
                ),
              ),
              Visibility(
                visible: isDone,
                child: const Divider(
                  color: Colors.black,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

here is where I listen for the change

Wrap(
      spacing: 13,
      runSpacing: 13,
      children: Provider.of<Storage>(context).dayTileMain,
   ),

when data['i'] is true, it should update the current instance of DayTile() that it's on in the loop, and in DayTile() I use the value of data['i'] to set the value of bool isDone and depending on whether isDone is true or false, the color of the widget changes and some other things, BUT, they don't change onTap, but they change after I hot restart, when it's read the storage and restored the saved data, could the secureStorage writing to storage at the same time be affecting it?

1

There are 1 best solutions below

0
On

I solved it, turns out it's not a good idea to listen for events in the model class, it won't listen, so instead of generating the list of widgets in the model class, I moved it outta there, and instead generated it inside the wrap widget, and instead of listening for a list in the model class, i just had the list there in my wrap, if it was a listview i was tryna generated, i would've done this initially with a ListView.builder() i didn't know you could generate a list inside the children of the wrap widget, so i just stuck to defining it in the model, I came across this stack question Flutter: How to use Wrap instead of ListView.builder? and that's how i knew how to build widgets inside a children property, i was actually just looking for a ListView.builder() version for the Wrap widget, all said, this is what my stuff is looking like

Model

class Storage extends ChangeNotifier {
  static const _storage = FlutterSecureStorage();
  static const _listKey = 'progress';
  List _dataMaps = [];
  List<DayTile> dayTileMain = [];

  void createDataMap() {
    for (int i = 1; i < 101; i++) {
      final data = Data(number: i).toJson();
      _dataMaps.add(data);
    }
  }

  int get listLength {
    return _dataMaps.length;
  }

  UnmodifiableListView get dataMaps {
    return UnmodifiableListView(_dataMaps);
  }

  void pressed(Map<String, dynamic> map) async {
    map['i'] = true;
    await writeToStorage();
    notifyListeners();
  }


  Future writeToStorage() async {
    final value = json.encode(_dataMaps);
    await _storage.write(key: _listKey, value: value);
  }

  Future<void> getTasks() async {
    print('getTasks called');
    final value = await _storage.read(key: _listKey);
    final taskList = value == null ? null : List.from(jsonDecode(value));
    if (taskList == null) {
      print('getTasks is null');
      createDataMap();
      // createDayTiles();
    } else {
      print('getTasks is not null');
      print(taskList);
      _dataMaps = taskList;
      // createDayTiles();
    }
  }

  Future readFromStorage() async {
    await getTasks();
    notifyListeners();
  }
}

Wrap Builder

class DayTiles extends StatelessWidget {
  const DayTiles({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Consumer<Storage>(
      builder: (_, storageData, __) => Wrap(
        spacing: 13,
        runSpacing: 13,
        children: [
          for(Map<String, dynamic> data in storageData.dataMaps)
            DayTile(
              number: data['n'],
              onTap: () {
                storageData.pressed(data);
              },
              isDone: data['i'],
            ),
        ],
      ),
    );
  }
}

and instead of using a wrap and listening for changes to it's children in the screen class, i just directly use the DayTiles() custom widget i created