My project is a quiz game. I have GameController class that extends ChangeNotifier and accessed throughout multiple widgets. in GameController I have a Game type attribute which has a list of teams List\<Player>
.
class Player {
String name;
int score;
dynamic historique;
void addScore(int x) {
this.score = x;
}
}
class Game {
Map<String, dynamic> gameOptions = {};
String mode = "";
List<Player> teamsList;
int nbRounds;
List<Question> questionsList; // number of questions should be a multiple of nbRounds
}
class GameController extends ChangeNotifier{
late Game game;
late int nbTeams;
late int _questionInd = 0;
late int _teamInd = 0;
int _timerState = 0;
int currentRound = 1;
bool responded = false;
bool gameOver = false;
set timerState(int value) {
_timerState = value;
notifyListeners();
}
setGame(Game game) {
this.game = game;
nbTeams = game.teamsList.length;
}
void nextQuestion() {
if (_questionInd%nbTeams >= nbTeams - 1) {
if (currentRound == game.nbRounds) {
gameOver = true;
return;
}
currentRound ;
}
_questionInd ;
_teamInd = (_teamInd 1) % nbTeams;
timerState = 0;
notifyListeners();
}
addResponse(int score) {
print("from controll");
print(getCurrentTeam().toString());
getCurrentTeam().addScore(score);
print(getCurrentTeam().toString());
responded = true;
notifyListeners();
}
testPlayer() { // outputs player1 30 player2 30
var player1 = getCurrentTeam();
var player2 = getCurrentTeam();
player1.addScore(10);
player2.addScore(20);
print("test player1");
print(player1.toString());
print("test player2");
print(player2.toString());
}
subResponse(int score) {
var team = getCurrentTeam();
team.score -= score;
responded = false;
notifyListeners();
}
int get questionInd => _questionInd;
int get timerState => _timerState;
Question getCurrentQuestion() {
return game.questionsList[_questionInd];
}
Player getCurrentTeam() {
return game.teamsList[_teamInd];
}
}
The problem is when I update a player's score, weirdly it is not persisted in the object and the score returns to default value zero when I go to next question in the questions list or next page which has results.
i have an answerCard widget that calls on addResponse/sub with GestureDetecture widget to add the score to the current team.
class AnswerCard extends StatefulWidget {
AnswerCard({Key? key, required this.answer, required this.score})
: super(key: key);
String answer;
int score;
@override
_AnswerCardState createState() => _AnswerCardState();
}
class _AnswerCardState extends State<AnswerCard> {
bool hiddenScore = true;
bool selected = false;
Color bgColor = CustomColor.yellow;
showScore() {
setState(() {
hiddenScore = !hiddenScore;
});
}
changeBg() {
setState(() {
bgColor = selected ? CustomColor.yellow : CustomColor.yellowDark;
});
}
@override
Widget build(BuildContext context) {
final GameController game = Provider.of<GameController>(context);
return GestureDetector(
onTap: () {
game.testPlayer();
if (game.timerState != 0){
showScore();
if (!selected) {
game.addResponse(widget.score);
} else {
game.subResponse(widget.score);
}
changeBg();
selected = !selected;
}
},
child: Container(
height: 8 * SizeConfig.defaultSize,
width: 8 * SizeConfig.defaultSize,
decoration: new BoxDecoration(
borderRadius: BorderRadius.circular(1.7 * SizeConfig.defaultSize),
color: bgColor,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(widget.answer),
SizedBox(
height: SizeConfig.defaultSize,
),
!hiddenScore
? Text(widget.score.toString())
: Image.asset(
'assets/icons/question_mark.webp',
height: 20,
width: 20,
),
],
),
),
);
}
}
this is home page widget simplified
class _HomeState extends State<Home> {
@override
Widget build(BuildContext context) {
var game = new Game(
teamsList: [
Player(name: "Aerobotix", score: 0),
Player(name: "IEEE", score: 0),
Player(name: "Theatro", score: 0),
],
nbRounds: 2,
questionsList: [
new Question(answersList: [
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
], question: "question1", timer: 30),
new Question(answersList: [
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
], question: "question2", timer: 30),
new Question(answersList: [
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
], question: "question3", timer: 30),
new Question(answersList: [
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
], question: "question4", timer: 30),
new Question(answersList: [
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
], question: "question5", timer: 30),
new Question(answersList: [
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
{"answer": "ijeba", "score": 10},
], question: "question6", timer: 30),
],
);
print("call build in home screen");
final GameController gameController = Provider.of<GameController>(context);
gameController.setGame(game);
return Scaffold(
body: ElevatedButton.icon(
icon: Icon(),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return StartGame();
},
),
);
},
);
}
}
CodePudding user response:
The problem resides in the _HomeState
, take a look at this snippet:
class _HomeState extends State<Home> {
@override
Widget build(BuildContext context) {
var game = new Game(
teamsList: [
Player(name: "Aerobotix", score: 0),
Player(name: "IEEE", score: 0),
Player(name: "Theatro", score: 0),
],
...
Every time Home
is redrawn/rebuilt the players are created again with score of 0. To fix that the players should be created in the _HomeState.initState()
. The trick to do that is to use a late
variable like so:
class _HomeState extends State<Home> {
late Game game;
@override
void initState() {
super.initState();
game = new Game(
teamsList: [
Player(name: "Aerobotix", score: 0),
Player(name: "IEEE", score: 0),
Player(name: "Theatro", score: 0),
],
...
);
}
@override
Widget build(BuildContext context) {
...
}
}
CodePudding user response:
i solved this after debugging. the problem was Game object was recreating in each notifyListeners() call in GameController. so i just added listen: false
since i only set the game and dont need to listen.