Flutter : Rebuild widget asynchrone with Provider, setState() or markNeedsBuild called during build

60 Views Asked by At

I have a problem with asynchrone in my piano app with flutter. It needs to play notes and change color on the keys when I call context.read().playNoteFromIndex(noteIndex, midiNote).

So, each time I call the playNoteFromIndex, the color of the key changes.

Here's the PianoKeyProvider :

Future playNoteFromIndex(int index, int midi) async { _colorList![index] = Colors.blue;

      notifyListeners();

      _flutterMidi.playMidiNote(midi: midi);

      await Future.delayed(Duration(milliseconds: 500));

      _colorList![index] = Colors.white;

      notifyListeners();
  }

In the build widget, I have a consumer that rebuild widgets when the color change : Flexible( flex: 1, child: Consumer( builder: (context, pianoKeysProvider, child) { return ListView.builder( itemCount: 7, scrollDirection: Axis.horizontal, controller: ScrollController(initialScrollOffset: 1500.0), itemBuilder: (BuildContext context, int index) { final int i = index * 12; return SafeArea( child: Stack( children: [ Row( mainAxisSize: MainAxisSize.min, children: [ _buildKey(24 + i, false, context, i), _buildKey(26 + i, false, context, i + 2), _buildKey(28 + i, false, context, i + 4), _buildKey(29 + i, false, context, i + 5), _buildKey(31 + i, false, context, i + 7), _buildKey(33 + i, false, context, i + 9), _buildKey(35 + i, false, context, i + 11), ], ),

I have another provider that changes the scale (set of notes to play on the keyboard) and I watch in my build widget for a change.

This is the call :

@override Widget build(BuildContext context) {

final ScaleProvider scaleProvider = context.watch<ScaleProvider>();
Scale? myScale = scaleProvider.getScale();

playScale(myScale!, context);

So I need to have a while loop that play notes one at the time in a function and calls the read for changing each note on the keyboard.

Future playScale(Scale scale, BuildContext context) async {

int currentIndex = 0;

while (_buttonPressPlay) {

if(currentIndex == scale.scaleLenght! - 1)
{
  currentIndex = 0;
}
else
{
  currentIndex++;
}

Note note = scale!.notes!.elementAt(currentIndex);

if (context.mounted)
{
  await context.read<PianoKeysProvider>().playNoteFromIndex(note.positionX, note.positionY);
}

await Future.delayed(Duration(milliseconds: 500));

} }

I got an exception : setState() or markNeedsBuild called during build

I tried to use future.delayed (the application stop being asynchrone or freeze at context.watch) or context.monted but same exception. Future.delayed(Duration.zero, () {

final ScaleProvider scaleProvider = context.watch<ScaleProvider>();
Scale? myScale = scaleProvider.getScale();
playScale(myScale!, context);

} });

Where I should go from there ? Any thoughts ? Thank you so much (first time posting on Stack Overflow :D )!

1

There are 1 best solutions below

3
On

you can use WidgetsBinding.instance.addPostFrameCallback() to make sure that setState() or notifyListeners() will not be called during build

WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
  setState(() {});
  notifyListeners();
});