'Is there a way I can get flutter to rebuild a gridview every time a flatbutton is pressed?

I am developing a maze app on flutter. I have a grid of 100 flatButtons that all start as grey. When a flatButton is pressed, I want it to turn green if the move is legitimate and red if the move is illegitimate (I used the splashColor property for this). Moves are constantly changing from illegitimate to legitimate (based on where the user currently is) and the splashColor of all the buttons should be dynamically updating. Right now, only the splashColor of a button only changes if I click it. I want the entire gridView of buttons to update their splashColor property whenever any button is pressed. Code is attached. Any help is much appreciated!

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:flutter/scheduler.dart' show timeDilation;
//lastmove initially set to 1 for simplicity.
int lastMove=1;
class GameButton extends StatefulWidget {
  final int id;
  int onPath=0;

  bool testOnPath(){
    if (this.onPath==1){
      return true;
    }
  else {
    return false;
    }
  }

  bool testIfLegal(lastMove){
    return (((this.id-lastMove).abs()==1) ^ ((this.id-lastMove).abs()==10));
  }

  bool moveCheck(){
    if(this.testIfLegal(lastMove) & this.testOnPath()){
      return true;
    }
    else{
      return false;
    }
  }

  GameButton(this.id, lastMove);
  @override
  _GameButtonState createState() => _GameButtonState();

}

class _GameButtonState extends State<GameButton> {
  @override
  Widget build(BuildContext context) {
    return Container(
        decoration: BoxDecoration(border: Border.all(color: Colors.black)),
      child: FlatButton(
          color:Colors.grey,
          splashColor: widget.moveCheck()?Colors.green:Colors.red,
          materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
          onPressed: () {
           bool a=widget.moveCheck();
           if(a){
             setState(() {
               lastMove=widget.id;
               print("GOOD MOVE");
             }
             );
           }
           else{
             print("Illegitmate Move");
           }
          },

          ),
    );
  }
}

class Maze extends StatefulWidget {
  List<GameButton> empty_grid = [for(var i=0; i<100; i++) new GameButton(i,lastMove)];
  @override
  _MazeState createState() => _MazeState();
}

class _MazeState extends State<Maze> {
  @override
  Widget build(BuildContext context) {
  }
}

void main() {
  //slow down animation to give user more time
  timeDilation = 3.0;
  Maze maze1 = new Maze();
  //filling maze manually-will be done with function in future
  (maze1.empty_grid[0]).onPath = 1;
  (maze1.empty_grid[10]).onPath = 1;
  (maze1.empty_grid[20]).onPath = 1;
  (maze1.empty_grid[30]).onPath = 1;
  (maze1.empty_grid[40]).onPath = 1;
  (maze1.empty_grid[50]).onPath = 1;
  (maze1.empty_grid[60]).onPath = 1;
  (maze1.empty_grid[70]).onPath = 1;
  (maze1.empty_grid[80]).onPath = 1;
  (maze1.empty_grid[90]).onPath = 1;
  (maze1.empty_grid[91]).onPath = 1;
  (maze1.empty_grid[92]).onPath = 1;
  (maze1.empty_grid[93]).onPath = 1;
  (maze1.empty_grid[94]).onPath = 1;
  (maze1.empty_grid[95]).onPath = 1;
  (maze1.empty_grid[96]).onPath = 1;
  (maze1.empty_grid[97]).onPath = 1;
  (maze1.empty_grid[98]).onPath = 1;
  (maze1.empty_grid[99]).onPath = 1;

//done filling maze1
    return runApp(
        MaterialApp(
              home: Scaffold(
                  backgroundColor: Colors.blue,
                  appBar: AppBar(
                    title: Text('Gorton Maze Test'),
                    backgroundColor: Colors.blueAccent,
                  ),
                  body: Center(
                        child: GridView.builder(
                          itemCount: 100,
                         gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 10, crossAxisSpacing: 0, mainAxisSpacing: 0),
                          shrinkWrap: true,
                          itemBuilder: (BuildContext context, int index){
                            return maze1.empty_grid[index];
                          },
                        )
                  ),
            ),
    )
    );
}


Solution 1:[1]

Yes. You can update entire GridView if you wrap by StreamController.

On the other hand, I don't think do everything in main class is a great idea. You should split class and function for easier. It'll make you code cleaner.

    body: HomePage()),

And in HomePage, I using stateful widget for more reasons.

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  StreamController _controller;

  @override
  void initState() {
    super.initState();
    _controller = new StreamController();
  }

  @override
  void dispose() {
    _controller.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
        child: StreamBuilder(
            stream: _controller.stream,
            builder: (_, __) {
              return GridView.builder(
                itemCount: 100,
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 10,
                    crossAxisSpacing: 0,
                    mainAxisSpacing: 0),
                shrinkWrap: true,
                itemBuilder: (BuildContext context, int index) {
                  return maze1.empty_grid[index];
                },
              );
            }));
  }
}

And every when you want to update your gridview. You can add StreamController with new value

_controller.add(null);

Hope this idea will help you.

Solution 2:[2]

With your existing design, you can achieve it by having a callback defined in GameButton that points to a method in the Maze class. Each time the button is pressed after changing the color of the button you call the callback into Maze class where you change the grid color as well. Sudo code:

class GameButton extends StatefulWidget {
    final VoidCallback clickCallback
    GameButton(this.id, lastMove, this.clickCallback);
    .....
}

class Maze extends StatefulWidget {
  List<GameButton> empty_grid = [for(var i=0; i<100; i++) new GameButton(i,lastMove, onButtonPressed)];
  @override
  _MazeState createState() => _MazeState();

  void onButtonPressed(){
     // change grid color based on index
  }
}

class _MazeState extends State<Maze> {
  @override
  Widget build(BuildContext context) {
  }
}

Solution 3:[3]

You can do it by calling the setstate method.

while clicking on the button after writing your logic use the setstate method. Basically setstate will rebuild the screen. so you can do something like this.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Loc Le Xuan
Solution 2 Sisir
Solution 3 DharmanBot