flutterwidgetstatefulrebuild

Flutter setState of *multiple instances* of a Stateful Widget in a Page


import 'dart:math';

import 'package:flutter/material.dart';

void main() {
  return runApp(
    MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.red,
        appBar: AppBar(
          title: Text('Dicee'),
          backgroundColor: Colors.red,
        ),
        body: DicePage(),
      ),
    ),
  );
}

class DicePage extends StatefulWidget {
  @override
  State<DicePage> createState() => _DicePageState();
}

class _DicePageState extends State<DicePage> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        children: [
          Die(DiePosition.Left),
          Die(DiePosition.Right),
        ],
      ),
    );
  }
}

enum DiePosition {
  Right,
  Left,
}

class Die extends StatefulWidget {
  final DiePosition diePosition;
  Die(this.diePosition);

  @override
  State<Die> createState() => _DieState();
}

class _DieState extends State<Die> {
  Map<DiePosition, int> dieNumbers = {
    DiePosition.Left: 1,
    DiePosition.Right: 1
  }; //

  Random rd = Random();

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: TextButton(
        child: Image.asset(
          'images/dice${widget.diePosition == DiePosition.Left ? dieNumbers[DiePosition.Left] : dieNumbers[DiePosition.Right]}.png',
        ),
        onPressed: () {
          setBothDiceState(rd);
          print(
              '${widget.diePosition.toString()} pressed! LB: ${dieNumbers[DiePosition.Left]} RB: ${dieNumbers[DiePosition.Right]}');
        },
      ),
    );
  }

  void setBothDiceState(Random rd) {
    setState(() {
      dieNumbers[DiePosition.Left] = rd.nextInt(6) + 1; // 1...6
      dieNumbers[DiePosition.Right] = rd.nextInt(6) + 1;
    }); // 1 - 6
  }

}

There are two Die instances in a row on the DicePage and I wish that whenever a press either of the dice, both update on screen.

However only the one I press updates on screen, in spite of both of the dieNumbers in the Map updating as expected.

Everything was working as expected until I tried to extract the Die class from the DicePage. I can't figure out how to rebuild both instances on the DicePage.


Solution

  • I made minimum changes so it should work like you want

    import 'dart:math';
    
    import 'package:flutter/material.dart';
    
    void main() {
      return runApp(
        MaterialApp(
          home: Scaffold(
            backgroundColor: Colors.red,
            appBar: AppBar(
              title: Text('Dicee'),
              backgroundColor: Colors.red,
            ),
            body: DicePage(),
          ),
        ),
      );
    }
    
    class DicePage extends StatefulWidget {
      @override
      State<DicePage> createState() => _DicePageState();
    }
    
    class _DicePageState extends State<DicePage> {
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Row(
            children: [
              Die(DiePosition.Left, setState),
              Die(DiePosition.Right, setState),
            ],
          ),
        );
      }
    }
    
    enum DiePosition {
      Right,
      Left,
    }
    
    class Die extends StatefulWidget {
      final DiePosition diePosition;
      Function(VoidCallback) setParent;
      Die(this.diePosition, this.setParent);
    
      @override
      State<Die> createState() => _DieState();
    }
    
    class _DieState extends State<Die> {
      static Map<DiePosition, int> dieNumbers = {
        DiePosition.Left: 1,
        DiePosition.Right: 1
      }; //
    
      Random rd = Random();
    
      @override
      Widget build(BuildContext context) {
        return Expanded(
          child: TextButton(
            child: Image.asset(
              'images/dice${widget.diePosition == DiePosition.Left ? dieNumbers[DiePosition.Left] : dieNumbers[DiePosition.Right]}.png',
            ),
            onPressed: () {
              setBothDiceState(rd);
              print(
                  '${widget.diePosition.toString()} pressed! LB: ${dieNumbers[DiePosition.Left]} RB: ${dieNumbers[DiePosition.Right]}');
            },
          ),
        );
      }
    
      void setBothDiceState(Random rd) {
        widget.setParent(() {
          dieNumbers[DiePosition.Left] = rd.nextInt(6) + 1; // 1...6
          dieNumbers[DiePosition.Right] = rd.nextInt(6) + 1;
        }); // 1 - 6
      }
    
    }
    

    So what I did is that each Die gets the function passed that calls setState for the parent. This way calling that will update both. Furthermore it was necessary to make the dieNumbers static because otherwise each Die has their own map