I have some local JSON data that I've parsed into my app. The data contains a few arrays and math questions (building a quiz app), so based on the math there are chapters which I've displayed in a ListView builder, when clicking on one of those it opens a new screen and passes on specific data for that chapter based on the index of the chapter.
In my quiz screen math questions in an array are brought from the JSON data depending on what chapter it is, I have the questions shuffled so that they appear in a random order, issue is when a user tries to answer a question in a provided TextField the keyboard opens as intended but the state of the UI changes which also changes the currently displayed question. How can I stop this?
To see the problem I have in the app here's a video link: https://youtu.be/n_VeT26I8NE
The JSON data has been parsed from a different screen and set in these variables:
List quiz = widget.newData["quiz"];
//Shuffle questions
var randomQuiz = (quiz..shuffle()).first;
If you prefer to read the Dart code from GitHub: https://github.com/BotsheloRamela/classio/blob/main/quiz_screen.dart
Some of the JSON data:
[
{
"chapter": "Exponents & Surds",
"introduction": {
"description": "A number's exponent determines how many times to multiply it.
Exponents can also be called powers or indices. In mathematics, a surd is a value
that cannot be further simplified into a whole number or integer. They are
irrational numbers.",
"videoID": ["568dGLFTom8", "XZRQhkii0h0"]
},
"quiz": [
{
"question": "Simplify the follwoing.",
"sum": "2a^2.3a^2.b^0",
"answer": "6a^5",
"explaination": ""
},
{
"question": "Rewrite the following with prime bases, your answer should be a fraction.",
"sum": "16.8^{-4}",
"answer": "1/256",
"explaination": ""
},
{
"question": "Solve the following, your answer should be a fraction.",
"sum": "\\frac{(-3a^3 b)^2}{a^5 b^3}",
"answer": "9a/b",
"explaination": ""
},
{
"question": "Rewrite the following with prime bases and simplify, your answer should be a fraction.",
"sum": "(\\frac{2x^3}{8y^{-4}})^{-3}",
"answer": "\\frac{64}{x^9 y^12}",
"explaination": ""
}
]
},
{
"chapter": "Equations & Inequalities",
"introduction": {
"description": "Equations and inequalities are both mathematical sentences made up of two expressions related to one another. The symbol = indicates that two expressions are regarded as equal within an equation. While in an inequality, two terms are not necessarily equal, which is indicated by the following symbols: >, <, ≤ or ≥.",
"videoID": ["hJ-_OoCHTks"]
},
"quiz": [
{
"question": "Show that the following trinomial is a perfect square.",
"sum": "x^2 2x 1",
"answer": "(x 1)^2",
"explaination": ""
},
{
"question": "Show that the following trinomial is a perfect square.",
"sum": "x^2-6x 9",
"answer": "(x-3)^2",
"explaination": ""
},
{
"question": "Show that the following trinomial is a perfect square.",
"sum": "x^2 8x 16",
"answer": "(x 4)^2",
"explaination": ""
}
]
},
]
Dart code:
import 'package:classio_students/bottom_nav.dart';
import 'package:classio_students/colors.dart';
import 'package:classio_students/screens/learn/maths_formula_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_math_fork/flutter_math.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:page_transition/page_transition.dart';
// ignore: must_be_immutable
class Gr11MathQuiz extends StatefulWidget {
var newData;
Gr11MathQuiz({Key? key, required this.newData}) : super(key: key);
@override
_Gr11MathQuizState createState() => _Gr11MathQuizState();
}
class _Gr11MathQuizState extends State<Gr11MathQuiz> {
final TextEditingController _answerController = TextEditingController();
@override
void initState() {
super.initState();
//SystemChannels.textInput.invokeMethod('TextInput.show');
}
@override
Widget build(BuildContext context) {
//Creates a list from the JSON data & shuffles the data quiz in a random order everytime the state changes
List quiz = widget.newData["quiz"];
var randomQuiz = (quiz..shuffle()).first;
//Check answer function
checkAnswer() async {
if (_answerController.text.toString() !=
randomQuiz['answer'].toString()) {
Fluttertoast.showToast(
msg: 'Are you sure? Take a look at your answer.',
gravity: ToastGravity.TOP,
backgroundColor: Colors.orangeAccent,
textColor: Colors.white);
} else {
Fluttertoast.showToast(
msg: 'Nice!',
gravity: ToastGravity.TOP,
backgroundColor: Colors.green,
textColor: Colors.white);
Future.delayed(const Duration(seconds: 1), () {
setState(() {});
_answerController.clear();
});
}
}
final height = MediaQuery.of(context).size.height;
const shape = StadiumBorder();
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: const Text('Gr11 Maths Quiz',
style: TextStyle(
color: titleColor,
letterSpacing: 1,
fontSize: 20,
fontWeight: FontWeight.bold)),
leading: IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(
Icons.arrow_back,
color: Colors.black,
size: 30,
)),
actions: [
IconButton(
onPressed: () => Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const CustomNavBar())),
icon: const Icon(
Icons.home,
color: Colors.black,
size: 30,
))
],
backgroundColor: Colors.white,
centerTitle: true,
elevation: 0,
),
body: SizedBox(
height: height,
child: Flexible(
child: Padding(
padding: const EdgeInsets.all(13),
child: Column(
children: [
Text(
randomQuiz['question'].toString(),
style: const TextStyle(color: Colors.black, fontSize: 17),
),
const SizedBox(
height: 30,
),
//Sum
Center(
child: Math.tex(
randomQuiz['sum'].toString(),
mathStyle: MathStyle.display,
textStyle: const TextStyle(fontSize: 25),
),
),
const SizedBox(
height: 60,
),
//Answer text
const Align(
alignment: Alignment.centerLeft,
child: Text(
'Answer:',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 20),
),
),
const SizedBox(
height: 10,
),
//Answer textfield & check btn
Row(
children: [
//Textfield
Expanded(
child: TextField(
controller: _answerController,
style: const TextStyle(color: Colors.black),
decoration: InputDecoration(
hintText: 'Answer',
hintStyle: const TextStyle(color: Colors.grey),
labelStyle: const TextStyle(color: Colors.grey),
enabledBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: Colors.grey),
borderRadius: BorderRadius.circular(13)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(13),
),
focusedBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: submitBtn),
borderRadius: BorderRadius.circular(13))),
),
),
const SizedBox(
width: 15,
),
//Button
Container(
decoration: BoxDecoration(
color: titleColor,
borderRadius: BorderRadius.circular(10)),
child: ElevatedButton(
onPressed: () {
checkAnswer();
},
child: const Text(
"Check",
style: TextStyle(fontSize: 16),
),
style: ElevatedButton.styleFrom(
primary: Colors.transparent,
shadowColor: Colors.transparent,
padding: const EdgeInsets.symmetric(
vertical: 19, horizontal: 20),
//minimumSize: const Size(100.0, 5.0),
textStyle: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 23)),
),
),
],
),
const SizedBox(
height: 20,
),
//View Math symbols dialog
Align(
alignment: Alignment.centerLeft,
child: Container(
decoration: BoxDecoration(
color: Colors.grey.shade400,
borderRadius: BorderRadius.circular(10)),
child: ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return const MathFormulaDialog();
});
},
child: const Text(
"How should I type my answer?",
style: TextStyle(fontSize: 16, color: Colors.black),
),
style: ElevatedButton.styleFrom(
primary: Colors.transparent,
shadowColor: Colors.transparent,
padding: const EdgeInsets.symmetric(
vertical: 19, horizontal: 20),
//minimumSize: const Size(100.0, 5.0),
textStyle: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 23)),
),
),
),
],
),
),
),
));
}
}
CodePudding user response:
try not to use code inside build
method unless you want it to be refreshed anytime any little change occurred in widget tree.
basically the problem is here in the very beginning lines of build
method:
...
@override
Widget build(BuildContext context) {
List quiz = widget.newData["quiz"];
var randomQuiz = (quiz..shuffle()).first;
...
you must use it outside of build
and only use it whenever an action is triggered for example if user finished answering current question then refresh ui and show another random question.
CodePudding user response:
Thanks to @Namini40, I moved the variables to initState()
from the build function, the checkAnswer()
function remained in the build method and I changed a couple things. If a user gets the answer right rather than using setState()
I instead used Navigator.pushReplacement()
to navigate to the very same page but will display a different question.
New code: https://github.com/BotsheloRamela/classio/blob/main/quiz_screen.dart