skip to Main Content

App is a simple memory/guessing game with a grid of squares. Floating action button triggers a "New game" dialog, and a Yes response triggers setState() on the main widget. The print() calls show it is building all the Tile widgets in the grid, but as it returns, the old grid values are still showing. Probably done something stupid but not seeing it. Basic code is below. TIA if anyone can see what is missing/invalid/broken/etc.

Main.dart is the usual main() that creates a stateless HomePage which creates a stateful widget which uses this State:

class MemHomePageState extends State<MemHomePage> {

  GameBoard gameBoard = GameBoard();
  GameController? gameController;
  int gameCount = 0, winCount = 0;

  @override
  void initState() {
    super.initState();
    gameController = GameController(gameBoard, this);
  }

  @override
  Widget build(BuildContext context) {
    if (kDebugMode) {
      print("MemHomepageState::build");
    }
    gameBoard.newGame(); // Resets secrets and grids
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: GridView.count(
          crossAxisCount: Globals.num_columns,
          children: List.generate(Globals.num_columns * Globals.num_rows, (index) {
            int x = index~/Globals.NR, y = index%Globals.NR;
            int secret = gameBoard.secretsGrid![x][y];
            var t = Tile(x, y, Text('$secret'), gameController!);
            gameBoard.tilesGrid![x].add(t);
            if (kDebugMode) {
              print("Row $x is ${gameBoard.secretsGrid![x]} ${gameBoard.tilesGrid![x][y].secret}");
            }
            return t;
          }),
        ),
        // Text("You have played $gameCount games and won $winCount."),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => newGameDialog("Start a new game?"),
        tooltip: 'New game?',
        child: const Icon(Icons.refresh_outlined),
      ),
    );
  }

  /** Called from the FAB and also from GameController "won" logic */
  void newGameDialog(String message) {
    showDialog<void>(
        context: context,
        barrierDismissible: false, // means the user must tap a button to exit the Alert Dialog
        builder: (BuildContext context) {
          return AlertDialog(
            title: Text("New game?"),
            content: Text(message),
            //),
            actions: <Widget>[
              TextButton(
                child: const Text('Yes'),
                onPressed: () {
                  setState(() {
                    gameCount++;
                  });
                  Navigator.of(context).pop();
                },
              ),
              TextButton(
                child: const Text('No'),
                onPressed: () {
                  Navigator.of(context).pop();
                },
              ),
            ],
          );
        }
    );
  }

The Tile class is a StatefulWidget whose state determines what that particular tile should show:

import 'package:flutter/material.dart';
import 'gamecontroller.dart';
    
enum TileMode {
      SHOWN,
      HIDDEN,
      CLEARED,
    }
    
/// Represents one Tile in the game
class Tile extends StatefulWidget {
  final int x, y;
  final Widget secret;
  final GameController gameController;
  TileState? tileState;

  Tile(this.x, this.y, this.secret, this.gameController, {super.key});

  @override
  State<Tile> createState() => TileState(x, y, secret);
    
  setCleared() {
    tileState!.setCleared();
  }
}
    
    class TileState extends State<Tile> {
      final int x, y;
      final Widget secret;
      TileMode tileMode = TileMode.HIDDEN;
    
      TileState(this.x, this.y, this.secret);
    
      _unHide() {
        setState(() => tileMode = TileMode.SHOWN);
        widget.gameController.clicked(widget);
      }
    
      reHide() {
        print("rehiding");
        setState(() => tileMode = TileMode.HIDDEN);
      }
    
      setCleared() {
        print("Clearing");
        setState(() => tileMode = TileMode.CLEARED);
      }
    
      _doNothing() {
        //
      }
    
      @override
      Widget build(BuildContext context) {
        switch(tileMode) {
          case TileMode.HIDDEN:
            return ElevatedButton(
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.teal,
                ),
                onPressed: _unHide,
                child: Text(''));
          case TileMode.SHOWN:
            return ElevatedButton(
                onPressed: _doNothing,
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.green,
                ),
                child: secret);
          case TileMode.CLEARED:
            return ElevatedButton(
                onPressed: _doNothing,
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.black12,
                ),
                child: const Icon(Icons.check));
        }
      }
    }

2

Answers


  1. Chosen as BEST ANSWER

    The original problem is that the Tile objects, although correctly created and connected to the returned main widget, did not have distinct 'key' values so they were not replacing the originals. Adding 'key' to the Tile constructor and 'key: UniqueKey()' to each Tile() in the loop, solved this problem. It exposed a related problem but is out of scope for this question. See the github link in the OP for the latest version.


  2. it looks like you are calling the following in your build function. That would cause everything to reset everytime it builds. Perhaps it belongs in init instead?

    gameBoard.newGame(); // Resets secrets and grids
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search