Reputation: 300
I'm studying Bloc and Cubit state manegement with a Tic Tac Toe game and I'm not being able to render the Board after a new player select a tile. Why BlocBuilder isn't building everytime I emit a new state?
Here is my GameCubit:
import 'package:flutter_bloc/flutter_bloc.dart';
import 'game_state.dart';
class GameCubit extends Cubit<GameState> {
GameCubit({
GameState? initialState,
}) : super(initialState ?? const GameState());
void makePlay({required int index, required String playerTurn}) {
if (state.board[index] != '') return;
List<String> actualBoard = List.from(state.board);
actualBoard[index] = playerTurn;
if (_hasWinner()) {
emit(
state.copyWith(
status: GameStatus.hasWinner,
gameWinner: playerTurn,
playerTurn: revertPlayerTurn(actualPlayer: playerTurn),
),
);
resetBoard();
} else if (_hasDraw()) {
emit(
state.copyWith(
status: GameStatus.hasDraw,
),
);
resetBoard();
} else {
return emit(
state.copyWith(
playerTurn: revertPlayerTurn(actualPlayer: playerTurn),
status: GameStatus.inProgress,
board: actualBoard,
),
);
}
}
String revertPlayerTurn({required String actualPlayer}) =>
actualPlayer == 'X' ? 'O' : 'X';
void resetBoard() => emit(
state.copyWith(
board: List.filled(9, '', growable: false),
status: GameStatus.initial,
),
);
bool _hasWinner() {
if (state.board[0] != '' &&
state.board[0] == state.board[1] &&
state.board[0] == state.board[2] ||
state.board[3] != '' &&
state.board[3] == state.board[4] &&
state.board[3] == state.board[5] ||
state.board[6] != '' &&
state.board[6] == state.board[7] &&
state.board[6] == state.board[8] ||
state.board[0] != '' &&
state.board[0] == state.board[4] &&
state.board[0] == state.board[8] ||
state.board[2] != '' &&
state.board[2] == state.board[6] &&
state.board[2] == state.board[4] ||
state.board[0] != '' &&
state.board[0] == state.board[3] &&
state.board[0] == state.board[6] ||
state.board[1] != '' &&
state.board[1] == state.board[4] &&
state.board[1] == state.board[7] ||
state.board[2] != '' &&
state.board[2] == state.board[5] &&
state.board[2] == state.board[8]) {
return true;
}
return false;
}
void increaseWinScore({required String winner}) {
if (winner == 'X') {
return emit(state.copyWith(xPlayerScore: state.xPlayerScore + 1));
} else {
return emit(state.copyWith(xPlayerScore: state.oPlayerScore + 1));
}
}
void increaseDrawScore() => emit(
state.copyWith(
drawGameScore: state.drawGameScore + 1,
),
);
void resetScore() => emit(
state.copyWith(
xPlayerScore: 0,
oPlayerScore: 0,
drawGameScore: 0,
status: GameStatus.initial),
);
bool _hasDraw() => !state.board.contains('');
}
Here is my GameState:
import 'package:equatable/equatable.dart';
enum GameStatus { initial, inProgress, hasWinner, hasDraw, error }
class GameState extends Equatable {
const GameState({
this.status = GameStatus.initial,
this.playerTurn = 'X',
this.drawGameScore = 0,
this.oPlayerScore = 0,
this.xPlayerScore = 0,
this.board = const ['', '', '', '', '', '', '', '', ''],
this.gameWinner,
});
final GameStatus status;
final String playerTurn;
final int drawGameScore;
final int oPlayerScore;
final int xPlayerScore;
final List<String> board;
final String? gameWinner;
GameState copyWith({
GameStatus? status,
String? playerTurn,
int? drawGameScore,
int? oPlayerScore,
int? xPlayerScore,
String? gameWinner,
List<String>? board,
}) {
return GameState(
status: status ?? this.status,
playerTurn: playerTurn ?? this.playerTurn,
gameWinner: gameWinner ?? this.gameWinner,
drawGameScore: drawGameScore ?? this.drawGameScore,
xPlayerScore: xPlayerScore ?? this.xPlayerScore,
oPlayerScore: oPlayerScore ?? this.oPlayerScore,
board: board ?? this.board,
);
}
@override
List<Object?> get props => <Object?>[];
}
And here is my Game screen:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../theme/colors.dart';
import '../bloc/game_cubit.dart';
import '../bloc/game_state.dart';
class Game extends StatelessWidget {
const Game({super.key, required this.title});
final String title;
void _showWinDialog({
required BuildContext context,
required GameCubit cubit,
required String winner,
}) {
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(' $winner venceu!'),
actions: [
TextButton(
child: const Text("Jogar novamente"),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
},
);
}
void _showDrawDialog({
required BuildContext context,
required GameCubit cubit,
}) {
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Empate!'),
actions: [
TextButton(
child: const Text("Jogar novamente"),
onPressed: () {
cubit.resetBoard();
Navigator.of(context).pop();
},
)
],
);
},
);
}
@override
Widget build(BuildContext context) {
final cubit = context.read<GameCubit>();
return BlocConsumer<GameCubit, GameState>(
listener: (context, state) {
if (state.status == GameStatus.hasWinner && state.gameWinner != null) {
_showWinDialog(
context: context,
cubit: cubit,
winner: state.gameWinner!,
);
}
if (state.status == GameStatus.hasDraw) {
_showDrawDialog(
context: context,
cubit: cubit,
);
}
},
builder: (context, state) {
return Scaffold(
backgroundColor: CoreColors.primaryColor,
appBar: AppBar(
title: Text(title),
backgroundColor: CoreColors.primaryColor,
),
body: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(vertical: 60),
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
padding: const EdgeInsets.all(16.0),
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
children: List.generate(
9,
(index) => InkWell(
onTap: () => cubit.makePlay(
index: index,
playerTurn: state.playerTurn,
),
child: Container(
height: 8,
width: 8,
decoration: const BoxDecoration(
color: Colors.white70,
borderRadius: BorderRadius.all(
Radius.circular(13),
),
),
child: Center(
child: Text(
state.board[index],
style: TextStyle(
fontSize: 70,
color: CoreColors.primaryColor,
),
),
),
),
),
),
),
),
Text(
'Agora é a vez do ${state.playerTurn} de jogar',
style: const TextStyle(fontSize: 18),
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 15),
),
Column(
children: [
Text('X venceu ${state.xPlayerScore} vez(es)'),
Text('O venceu ${state.oPlayerScore} vez(es)'),
Text('Empatou ${state.drawGameScore} vez(es)'),
],
),
],
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
cubit.resetBoard();
cubit.resetScore();
},
label: const Text(
'Reset',
style: TextStyle(color: Colors.white),
),
icon: const Icon(Icons.replay_circle_filled, color: Colors.white),
backgroundColor: Colors.pink,
),
);
},
);
}
}
I've tried with BlocConsumer too. But the builder method isn't being called even if the cubit.makePlay (the method that emit a new state) is called.
Upvotes: 0
Views: 37
Reputation: 300
I found the answer. I forgot to override the getters to all the props. So the Equatable wasn't able to identify that the GameState had changes.
I only edited this in my GameState and worked as expected:
@override
List<Object?> get props => <Object?>[
status,
playerTurn,
drawGameScore,
oPlayerScore,
xPlayerScore,
gameWinner,
board,
];
Upvotes: 0