I’m trying to build a sudoku application in flutter but the problem is it needs a continue button in it but I don’t know how to save current played game data in the Json.
class GameController extends GetxController {
Timer? _timer;
int remainingSeconds = 1;
final time = '00:00'.obs;
RxList<List<SudokuCell>> sudoku = RxList<List<SudokuCell>>();
RxInt mistakes = 3.obs;
RxInt hints = 3.obs;
SudokuCell selectedSudoku = SudokuCell(
text: 0,
correctText: 0,
row: 100,
col: 100,
team: 100,
isFocus: false,
isCorrect: false,
isDefault: false,
isExist: false,
note: []);
RxBool isNote = false.obs;
bool restartClicked = false;
@override
void onReady() {
_startTimer(900);
super.onReady();
}
@override
void onClose() {
if (_timer == null || stopTime == true) {
_timer!.cancel();
}
}
void restart(int difficultyLevel) {
mistakes.value = 3;
hints.value = 3;
sudoku.clear();
restartClicked = true;
List<List<int>> boxValues = s.generator(difficultyLevel: difficultyLevel);
List<List<int>> boxValueSolution = s.toSudokuList(boxValues);
s.solver(boxValueSolution);
for (var i = 0; i < 9; i++) {
sudoku.add([]);
for (var j = 0; j < 9; j++) {
int team = 0;
if (i < 3 && j < 3) {
team = 1;
} else if (i < 3 && j < 6) {
team = 2;
} else if (i < 3 && j < 9) {
team = 3;
} else if (i < 6 && j < 3) {
team = 4;
} else if (i < 6 && j < 6) {
team = 5;
} else if (i < 6 && j < 9) {
team = 6;
} else if (i < 9 && j < 3) {
team = 7;
} else if (i < 9 && j < 6) {
team = 8;
} else if (i < 9 && j < 9) {
team = 9;
}
SudokuCell value = SudokuCell(
text: boxValues[i][j],
correctText: boxValueSolution[i][j],
row: i,
col: j,
team: team,
isFocus: false,
isCorrect: boxValues[i][j] == boxValueSolution[i][j],
isDefault: boxValues[i][j] != 0,
isExist: false,
note: []);
sudoku[i].add(value);
}
}
}
isComplete() {
bool isComplete = true;
for (var i = 0; i < sudoku.length; i++) {
for (var j = 0; j < sudoku.length; j++) {
if (sudoku[i][j].text == 0) {
isComplete = false;
}
}
}
if (isComplete == true) {
levelCompleted();
}
}
void onErase() {
if (_unChangable()) return;
sudoku[selectedSudoku.row][selectedSudoku.col].text = 0;
sudoku[selectedSudoku.row][selectedSudoku.col].isCorrect = false;
sudoku[selectedSudoku.row][selectedSudoku.col].note.clear();
selectedSudoku.text = 0;
selectedSudoku.isCorrect = false;
selectedSudoku.note.clear();
update();
}
void onNoteFill() {
if (_unChangable()) return;
sudoku[selectedSudoku.row][selectedSudoku.col].note =
List.generate(9, (index) => index + 1);
fetchSafeValues();
update();
}
// ignore: duplicate_ignore
void onHint() {
if (_unChangable()) return;
// ignore: unrelated_type_equality_checks
if (hints == 0) return;
sudoku[selectedSudoku.row][selectedSudoku.col].text =
sudoku[selectedSudoku.row][selectedSudoku.col].correctText;
sudoku[selectedSudoku.row][selectedSudoku.col].isCorrect = true;
removeNoteValue(sudoku[selectedSudoku.row][selectedSudoku.col].correctText);
isComplete();
hints--;
}
void onNumberclick(int index) {
if (selectedSudoku.row == 100) return;
if (isNote.value) {
if (sudoku[selectedSudoku.row][selectedSudoku.col]
.note
.contains(index + 1)) {
sudoku[selectedSudoku.row][selectedSudoku.col].note.remove((index + 1));
} else {
sudoku[selectedSudoku.row][selectedSudoku.col].note.add((index + 1));
}
fetchSafeValues();
} else {
if (selectedSudoku.isCorrect) return;
selectedSudoku.text = index + 1;
selectedSudoku.isCorrect =
selectedSudoku.text == selectedSudoku.correctText;
sudoku[selectedSudoku.row][selectedSudoku.col] = selectedSudoku;
if (selectedSudoku.correctText != (index + 1)) {
mistakes--;
if (mistakes == 0.obs) {
showRestartDialogue('Game Over!');
}
} else {
removeNoteValue(index + 1);
}
isComplete();
}
update();
}
bool _unChangable() {
if (selectedSudoku.row == 100) return true;
if (selectedSudoku.isDefault) return true;
return false;
}
void showRestartDialogue(String text) => Get.defaultDialog(
backgroundColor: Colors.blue.shade50,
barrierDismissible: false,
buttonColor: Colors.greenAccent,
title: text,
content: SizedBox(
height: 200,
child: Column(
children: [
TextButton(
onPressed: () {
restart(1);
Get.back();
},
child: const Text('Beginner')),
TextButton(
onPressed: () {
restart(2);
Get.back();
},
child: const Text('Easy')),
TextButton(
onPressed: () {
restart(3);
Get.back();
},
child: const Text("Medium")),
TextButton(
onPressed: () {
restart(4);
Get.back();
},
child: const Text('Hard')),
],
),
),
);
bool isSafe(int row, int col) {
return selectedSudoku.col == sudoku[row][col].col ||
selectedSudoku.row == sudoku[row][col].row ||
selectedSudoku.team == sudoku[row][col].team;
}
void fetchSafeValues() {
List<int> safeValues = [];
for (var i = 0; i < 9; i++) {
for (var j = 0; j < 9; j++) {
if (selectedSudoku.row == i) {
safeValues.add(sudoku[i][j].text);
} else if (selectedSudoku.col == j) {
safeValues.add(sudoku[i][j].text);
} else if (selectedSudoku.team == sudoku[i][j].team) {
safeValues.add(sudoku[i][j].text);
}
}
}
safeValues.removeWhere((element) => element == 0);
for (var value in safeValues) {
sudoku[selectedSudoku.row][selectedSudoku.col].note.remove(value);
selectedSudoku.note.remove(value);
}
}
void removeNoteValue(int number) {
for (var i = 0; i < 9; i++) {
for (var j = 0; j < 9; j++) {
if (isSafe(i, j)) {
sudoku[i][j].note.remove(number);
}
}
}
}
_startTimer(int seconds) {
const duration = Duration(seconds: 1);
remainingSeconds = seconds;
_timer = Timer.periodic(duration, (Timer timer) {
if (remainingSeconds == 0 || isComplete() == true) {
timer.cancel();
return showGameOverDialog();
} else {
int minutes = remainingSeconds ~/ 60;
int seconds = (remainingSeconds % 60);
time.value =
"${minutes.toString().padLeft(2, "0")}:${seconds.toString().padLeft(2, "0")}";
remainingSeconds--;
}
});
}
restartTimer(int seconds) {
const duration = Duration(seconds: 1);
remainingSeconds = seconds;
_timer = Timer.periodic(duration, (Timer timer) {
if (remainingSeconds == 0 || isComplete() == true) {
timer.cancel();
return showGameOverDialog();
} else {
int minutes = remainingSeconds ~/ 60;
int seconds = (remainingSeconds % 60);
time.value =
"${minutes.toString().padLeft(2, "0")}:${seconds.toString().padLeft(2, "0")}";
remainingSeconds--;
}
});
}
void showGameOverDialog() => Get.defaultDialog(
title: 'Game Over',
content: SizedBox(
height: 50,
child: TextButton(
onPressed: () => Get.back(),
child: const Text('Start New Game!'))));
void levelCompleted() => Get.defaultDialog(
backgroundColor: Colors.blue.shade50,
title: 'Level Completed',
content: SizedBox(
height: 100,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const Text("Earned a star"),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.blue.shade400),
textStyle: MaterialStateProperty.all(
const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
onPressed: () {
Get.offAll(const HomeScreen());
},
child: const Text('Main Menu'),
),
ElevatedButton(
onPressed: () {
showRestartDialogue('Choose difficulty');
},
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.blue.shade400),
textStyle: MaterialStateProperty.all(
const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
child: const Text('Next Game'),
)
],
),
],
),
),
);
Widget button(String text, difficulty) {
return TextButton(
onPressed: () {
showRestartDialogue('Choose difficulty');
Get.back();
},
style: ButtonStyle(
textStyle: MaterialStateProperty.all(
const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
child: Text(
text,
style: TextStyle(color: Colors.blue.shade300),
),
);
}
}
this is my game controller class
and
class SudokuCell {
int text;
int correctText;
int row;
int col;
int team;
bool isFocus;
bool isCorrect;
bool isDefault;
bool isExist;
List<int> note;
SudokuCell({
required this.text,
required this.correctText,
required this.row,
required this.col,
required this.team,
required this.isFocus,
required this.isCorrect,
required this.isDefault,
required this.isExist,
required this.note,
});
SudokuCell copyWith({
int? text,
int? correctText,
int? row,
int? col,
int? team,
int? difficulty,
bool? isFocus,
bool? isCorrect,
bool? isDefault,
bool? isExist,
List<int>? note,
}) {
return SudokuCell(
text: text ?? this.text,
correctText: correctText ?? this.correctText,
row: row ?? this.row,
col: col ?? this.col,
team: team ?? this.team,
isFocus: isFocus ?? this.isFocus,
isCorrect: isCorrect ?? this.isCorrect,
isDefault: isDefault ?? this.isDefault,
isExist: isExist ?? this.isExist,
note: note ?? this.note,
);
}
Map<String, dynamic> toMap() {
return <String, dynamic>{
'text': text,
'correctText': correctText,
'row': row,
'col': col,
'team': team,
'isFocus': isFocus,
'isCorrect': isCorrect,
'isDefault': isDefault,
'isExist': isExist,
'note': note,
};
}
factory SudokuCell.fromMap(Map<String, dynamic> map) {
return SudokuCell(
text: map['text'] as int,
correctText: map['correctText'] as int,
row: map['row'] as int,
col: map['col'] as int,
team: map['team'] as int,
isFocus: map['isFocus'] as bool,
isCorrect: map['isCorrect'] as bool,
isDefault: map['isDefault'] as bool,
isExist: map['isExist'] as bool,
note: List<int>.from(
(map['note'] as List<int>),
),
);
}
String toJson() => json.encode(toMap());
factory SudokuCell.fromJson(String source) =>
SudokuCell.fromMap(json.decode(source) as Map<String, dynamic>);
@override
String toString() {
return 'SudokuCell(text: $text, correctText: $correctText, row: $row, col: $col, team: $team,isFocus: $isFocus, isCorrect: $isCorrect, isDefault: $isDefault, isExist: $isExist, note: $note)';
}
@override
bool operator ==(covariant SudokuCell other) {
if (identical(this, other)) return true;
return other.text == text &&
other.correctText == correctText &&
other.row == row &&
other.col == col &&
other.team == team &&
other.isFocus == isFocus &&
other.isCorrect == isCorrect &&
other.isDefault == isDefault &&
other.isExist == isExist &&
listEquals(other.note, note);
}
@override
int get hashCode {
return text.hashCode ^
correctText.hashCode ^
row.hashCode ^
col.hashCode ^
team.hashCode ^
isFocus.hashCode ^
isCorrect.hashCode ^
isDefault.hashCode ^
isExist.hashCode ^
note.hashCode;
}
}
this is the model file for everything
I’m saving the json in sharedpreferences to access it but it shows exception about being the values are null.
2
Answers
You can use, Hive local database to save json files according to your needs,
https://pub.dev/packages/hive
See this example,
You are probably trying to save the data in json so that you can use it after reopening the app. In such case, you don’t need to save json file. Instead you use storage managers to save data locally into the device. There are 3 Popular Storage managers:
As you are already using Getx, I would suggest you to use Get Storage
In your main function init GetStorage
To read or write to your box or local storage create a box instance
After that, convert your suduko list instance to its toJson format.
Then json encode your sudukoMap and save it to the box with a key value
Now it is now saved locally so where ever you want to access it, just read from box
Don’t forget to json decode the list with SudokuCell.fromJson() and jsonDecode the string. Remember, we are actually saving the object as string as GetStorage only supports primitive type