Почему все повторно инициализируется каждую секунду после добавления периодического таймера во Flutter?
Я пытаюсь создать приложение для викторин. Когда любой пользователь начинает участвовать в викторине, запускается таймер, и он не знает, почему все повторно инициализируется каждую секунду. Логический параметр "ответ" устанавливается как ложное каждую секунду. В результате участник может ответить на один и тот же вопрос несколько раз, что приведет к неправильному результату и не может произойти. Вот отрывок-
class MathQuizPlay extends StatefulWidget {
final String quizId;
MathQuizPlay({Key key, this.quizId}) : super(key: key);
@override
_MathQuizPlayState createState() => _MathQuizPlayState(this.quizId);
}
int total = 0;
int _correct = 0;
int _incorrect = 0;
int _notAttempted = 0;
int timer;
String showtimer;
class _MathQuizPlayState extends State<MathQuizPlay> {
var quizId;
_MathQuizPlayState(this.quizId);
QuerySnapshot questionSnapshot;
bool isLoading = true;
getQuestionData(String quizId) async {
return await Firestore.instance
.collection('math')
.document(quizId)
.collection('QNA')
.getDocuments();
}
@override
void initState() {
getQuestionData(quizId).then((value) {
questionSnapshot = value;
setState(() {
total = questionSnapshot.documents.length;
_correct = 0;
_incorrect = 0;
_notAttempted = questionSnapshot.documents.length;
isLoading = false;
timer = total * 15;
showtimer = timer.toString();
});
});
starttimer();
super.initState();
}
@override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
Questions getQuestionModelFromDatasnapshot(
DocumentSnapshot questionSnapshot) {
final Questions questionModel = Questions(
question: questionSnapshot.data['question'],
option1: questionSnapshot.data['option1'],
option2: questionSnapshot.data['option2'],
option3: questionSnapshot.data['option3'],
option4: questionSnapshot.data['option4'],
correctOption: questionSnapshot.data['correctOption'],
answered: false);
return questionModel;
}
void starttimer() async {
const onesec = Duration(seconds: 1);
Timer.periodic(onesec, (Timer t) {
setState(() {
if (timer < 1) {
t.cancel();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => Result(
correct: _correct,
incorrect: _incorrect,
total: total,
notattempt: _notAttempted,
collection: 'math',
quizId: quizId,
)));
} else {
timer = timer - 1;
}
showtimer = timer.toString();
});
});
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.teal[300],
title: Text("Questions",
style: TextStyle(
color: Colors.white,
)),
elevation: 5.0,
centerTitle: true,
),
body: isLoading
? Container(
child: Center(child: CircularProgressIndicator()),
)
: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
height: 10,
),
Center(
child: Container(
height: 60,
width: 60,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(36),
border: Border.all(
width: 2.0,
color: Colors.red.withOpacity(0.8),
),
),
child: Center(
child: Text(
showtimer,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 19.0,
color: Colors.red.withOpacity(0.8)),
)))),
SizedBox(
height: 10,
),
Center(child: Text('Tap on the option to select answer')),
SizedBox(
height: 10,
),
questionSnapshot.documents == null
? Container(
child: Center(
child: Text("No Data"),
),
)
: ListView.builder(
itemCount: questionSnapshot.documents.length,
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 15.0, horizontal: 25),
child: QuizPlayTile(
questionModel: getQuestionModelFromDatasnapshot(
questionSnapshot.documents[index]),
index: index,
),
);
}),
SizedBox(
height: 30,
),
Center(
child: RaisedButton(
padding:
EdgeInsets.symmetric(vertical: 18, horizontal: 60),
color: Colors.teal[300],
textColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(8.0)),
child: Text(
'Submit',
style: TextStyle(fontSize: 16),
),
elevation: 7.0,
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => Result(
correct: _correct,
incorrect: _incorrect,
total: total,
notattempt: _notAttempted,
collection: 'math',
quizId: quizId,
)));
}),
),
SizedBox(
height: 50,
)
],
),
),
);
}
}
2 ответа
Я тоже не смог решить проблему с помощью ChangeNotifierProvider. Но, к счастью, я нашел пакет, который полностью решил мою проблему. Поэтому я использовал этот пакет вместо периодического таймера для установки времени. Вот обновление -
import 'package:circular_countdown_timer/circular_countdown_timer.dart';
class PhyQuizPlay extends StatefulWidget {
final String quizId;
PhyQuizPlay({Key key, this.quizId}) : super(key: key);
@override
_PhyQuizPlayState createState() => _PhyQuizPlayState(this.quizId);
}
int total = 0;
int _correct = 0;
int _incorrect = 0;
int _notAttempted = 0;
int timer;
class _PhyQuizPlayState extends State<PhyQuizPlay> {
var quizId;
_PhyQuizPlayState(this.quizId);
QuerySnapshot questionSnapshot;
bool isLoading = true;
getQuestionData(String quizId) async {
return await Firestore.instance
.collection('physics')
.document(quizId)
.collection('QNA')
.getDocuments();
}
@override
void initState() {
getQuestionData(quizId).then((value) {
questionSnapshot = value;
setState(() {
total = questionSnapshot.documents.length;
_correct = 0;
_incorrect = 0;
_notAttempted = total;
isLoading = false;
timer = total * 15;
});
});
super.initState();
}
Questions getQuestionModelFromDatasnapshot(
DocumentSnapshot questionSnapshot) {
final Questions questionModel = Questions(
question: questionSnapshot.data['question'],
option1: questionSnapshot.data['option1'],
option2: questionSnapshot.data['option2'],
option3: questionSnapshot.data['option3'],
option4: questionSnapshot.data['option4'],
correctOption: questionSnapshot.data['correctOption'],
answered: false);
return questionModel;
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.teal[300],
title: Text("Questions",
style: TextStyle(
color: Colors.white,
)),
elevation: 5.0,
centerTitle: true,
),
body: isLoading
? Container(
child: Center(child: CircularProgressIndicator()),
)
: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
height: 10,
),
Center(
child: CircularCountDownTimer(
width: 80,
height: 80,
duration: timer,
fillColor: Colors.red,
color: Colors.white38,
isReverse: true,
onComplete: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => Result(
correct: _correct,
incorrect: _incorrect,
total: total,
notattempt: _notAttempted,
collection: 'physics',
quizId: quizId,
)));
},
)),
SizedBox(
height: 10,
),
Center(child: Text('Tap on the option to select answer')),
SizedBox(
height: 10,
),
questionSnapshot.documents == null
? Center(
child: CircularProgressIndicator(),
)
: ListView.builder(
itemCount: questionSnapshot.documents.length,
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 15.0, horizontal: 25),
child: QuizPlayTile(
questionModel: getQuestionModelFromDatasnapshot(
questionSnapshot.documents[index]),
index: index,
),
);
}),
SizedBox(
height: 30,
),
Center(
child: RaisedButton(
padding:
EdgeInsets.symmetric(vertical: 18, horizontal: 60),
color: Colors.teal[300],
textColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(8.0)),
child: Text(
'Submit',
style: TextStyle(fontSize: 16),
),
elevation: 7.0,
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => Result(
correct: _correct,
incorrect: _incorrect,
total: total,
notattempt: _notAttempted,
collection: 'physics',
quizId: quizId,
)));
}),
),
SizedBox(
height: 50,
)
],
),
),
);
}
}
ВЫПУСК:
Поскольку вы вызываете setState каждый раз, когда ваш таймер завершает секунду... он обновляет ваш экран каждую секунду. Следовательно, функция сборки повторно вызывается после каждой секунды перестройки каждой любой строки кода внутри этой функции. Теперь основная проблема заключается в том, что функция getQuestionModelFromDatasnapshot всегда будет предоставлять новую модель questionModel со значением параметра "отвечено" как false каждую секунду, когда экран обновляется.
РЕШЕНИЕ:
Теперь у меня есть два решения:
Хорошая практика программирования: я предполагаю, что вы вызываете setState, чтобы обновлять значение таймера в пользовательском интерфейсе каждые секунды. Теперь вы должны понять, что вызов setState для небольшого изменения - это плохо, поскольку он обновит все другие виджеты, которые также будут воссозданы, что не нужно. Хороший способ - использовать ChangeNotifierProvider, который будет прослушивать обновления в таймере, а затем переносить текст пользовательского интерфейса, показывающий значение таймера, с помощью Consumer. Следовательно, всякий раз, когда значение таймера обновляется... Он будет обновлять только текст пользовательского интерфейса со значением таймера.
Если вам нужно быстрое решение: вместо вызова метода getQuestionModelFromDatasnapshot в функции сборки вы можете инициализировать весь ListView в initstate как переменную.... например: ListView x = /* Код вашего списка, содержащий это fucntion */ ..... Делая это...... виджет не будет перестраиваться каждую секунду.
Код немного запутан... Я настоятельно предпочитаю либо переписать код должным образом, либо использовать ChangeNotifierProvider, как я сказал в первом варианте.