2

Before, I used ListBuilder to generate a list of 70 numbers and it worked but it took a long time to generate the 70 numbers into custom widgets and also when I tapped a number just to change the background color state, it took a couple of milliseconds before the state being changed.

ow

Now I am using a FutureBuilder to be able to load the screen while waiting for the generated 70 integers. But when I tap on the ball number, the background color is not updated ... It's like setState() is not working in Future ListBuilder.

This question: "Flutter - How to update state (or value?) of a Future/List used to build ListView (via FutureBuilder)" is very similar, but it did not solve my problem.

Here is the code I have in the build method

Flexible(
                child:FutureBuilder<List<Widget>>(
                  future: ballNumbers,
                  builder: (context, snapshot){
                    if(snapshot.connectionState != ConnectionState.done){
                      return Center(child: CircularProgressIndicator());
                    }
                    if(snapshot.hasError){
                      return Center(child: Text("An error has occured"));
                    }
                    List<Widget> balls = snapshot.data ?? [];
                    return GridView.count(
                      crossAxisCount: 9,
                      children: balls,
                    );
                  }
                )

Here is how I start the state for the function:

Future<List<Widget>> ballNumbers;
List<int> picks = []; 

@override
void initState() {
    ballNumbers = getBallNumbers();
});

  Future<List<Widget>> getBallNumbers() async {
    return List.generate(limitBallNumber,(number){
      number = number + 1;
      return Padding(
        padding:EdgeInsets.all(2.5),
        child:Ball(
          number : number,
          size: ballWidth,
          textColor:(picks.contains(number)) ? Colors.black : Colors.white,
          ballColor: (picks.contains(number)) ? Style.selectedBallColor : Style.ballColor,
          onTap:(){
            setState((){
                picks.contains(number) ? picks.remove(number) : picks.add(number);
            });
          }
        )
      );
    });
  }

UPDATED: Here is the class the Ball widget

class Ball extends StatelessWidget {
  final Color ballColor;
  final Color textColor;
  final double size;
  final double fontSize;
  final int number;
  final VoidCallback onTap;

  Ball({Key key, @required this.number, 
    this.textColor, 
    this.ballColor,
    this.onTap,
    this.size = 55.0,
    this.fontSize = 14,
  }) : super(key : key);

  @override
  Widget build(BuildContext context) {
    return Container(
      height: size,
      width: size,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        gradient: LinearGradient(
          colors: [
            Style.secondaryColor,
            ballColor != null ? ballColor : Style.ballColor,
          ],
          begin: Alignment.bottomLeft,
          end: Alignment.topRight
        )
      ),
      child: FlatButton(
        padding: EdgeInsets.all(0),
        child: Container(
          child: Text(
            number.toString().length > 1 ? number.toString() : "0" + number.toString(),
            style: TextStyle(
              fontSize: fontSize,
              color: textColor != null ? textColor : Colors.white
            ),
          ),
          padding: const EdgeInsets.all(4.0),
          decoration:BoxDecoration(
            color: Colors.transparent,
            border: Border.all(color: textColor != null ? textColor : Colors.white,  width: 1),
            borderRadius: BorderRadius.circular(32),
          )
        ),
        color: Colors.transparent,
        onPressed: onTap,
      ),
    );
  }
}
nvoigt
  • 75,013
  • 26
  • 93
  • 142
Lemayzeur
  • 8,297
  • 3
  • 23
  • 50

1 Answers1

1

The issue is that getBallNumbers is only being called once in initState, so when picks is updated, it doesn't matter because getBallNumbers isn't called again to update the colors being passed to the Ball widgets.

A simple fix would be to call getBallNumbers in your build with future: getBallNumbers(), but this would lead to the CircularProgressIndicator being shown on every click as the List regenerates.

However, ideally, you should be handling all of the color changing within the state of each Ball so that you're not forced to rebuild that List on every click. And to maintain a List of selected numbers within the parent widget's State, you should pass a callback to each ball that adds and removes their number from the List in the parent.


Rough example:

Ball class(modified to be stateful and removed parameters that became unecessary; active state is now stored within the ball instead of solely in the parent):

class Ball extends StatefulWidget {
  final double size;
  final double fontSize;
  final int number;
  final VoidCallback toggleBall;
  final bool initialActiveState;

  Ball({Key key, @required this.number, 
    this.toggleBall,
    this.size = 55.0,
    this.fontSize = 14,
    this.initialActiveState,
  }) : super(key : key);

  _BallState createState() => _BallState();
}

class _BallState extends State<Ball> {
  bool isActive;
  
  @override
  void initState() {
    super.initState();
    isActive = widget.initialActiveState;
  }
  
  @override
  Widget build(BuildContext context) {
    return Container(
      height: widget.size,
      width: widget.size,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        gradient: LinearGradient(
          colors: [
            Style.secondaryColor,
            isActive ? Style.selectedBallColor : Style.ballColor,
          ],
          begin: Alignment.bottomLeft,
          end: Alignment.topRight
        )
      ),
      child: FlatButton(
        padding: EdgeInsets.all(0),
        child: Container(
          child: Text(
            widget.number.toString().length > 1 ? widget.number.toString() : "0" + widget.number.toString(),
            style: TextStyle(
              fontSize: widget.fontSize,
              color: isActive ? Colors.black : Colors.white,
            ),
          ),
          padding: const EdgeInsets.all(4.0),
          decoration:BoxDecoration(
            color: Colors.transparent,
            border: Border.all(color: isActive ? Colors.black : Colors.white,  width: 1),
            borderRadius: BorderRadius.circular(32),
          )
        ),
        color: Colors.transparent,
        onPressed: () {
          if(!isActive && widget.activeBallList.length >= 7) {
            return;
          }
          setState(() {
            isActive = !isActive;
          });
          widget.activeBallList.contains(widget.number) ? widget.activeBallList.remove(widget.number) : widget.activeBallList.add(widget.number);
        },
      ),
    );
  }
}

Parent class(the only part that needs to be modified is the parameters for Ball):

Future<List<Widget>> getBallNumbers() async {
  return List.generate(limitBallNumber,(number){
    number = number + 1;
    return Padding(
      padding:EdgeInsets.all(2.5),
      child: Ball(
        number: number,
        size: ballWidth,
        initialActiveState: picks.contains(number),
        activeBallList: picks,
      )
    );
  });
}
Not A Bot
  • 168
  • 7
  • Can you please show an example based on what you explained? – Lemayzeur Jul 27 '20 at 02:51
  • @Lemayzeur For the "ideal" solution? Also where are you calling `setState`? – Not A Bot Jul 27 '20 at 02:52
  • Yes please. I am a little new to Flutter. but it seems that your solution is going to work – Lemayzeur Jul 27 '20 at 02:53
  • Ow sorry! I missed it in the code, I call it in the onTap function callback – Lemayzeur Jul 27 '20 at 02:54
  • @Lemayzeur Yeah I can make a sample. Just letting you know it'll take a while. Can you show `Ball` class as well? – Not A Bot Jul 27 '20 at 02:56
  • Just updated the code, Ball class added. Thank you! – Lemayzeur Jul 27 '20 at 02:58
  • @Lemayzeur I posted the example. Not as much needed to be changed in the parent class as I originally thought, so my previous explanation might've been a bit over-complicated. I can't really test this so let me know of the result. – Not A Bot Jul 27 '20 at 03:26
  • Hello Not A Bot, Your solution works like charm. But there is a limitation of 7 balls, I see that now I can select without that limit... do you know how to have that limitation up to 7 balls selected? picks must have at more 7 balls, and after that the user can not change any balls – Lemayzeur Jul 27 '20 at 12:17
  • @Lemayzeur So if you try to generate a `List` of more than 7 balls it just stops working? The active state of each balls just stops changing? – Not A Bot Jul 27 '20 at 13:44
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/218687/discussion-between-lemayzeur-and-not-a-bot). – Lemayzeur Jul 27 '20 at 14:12