I am building a quiz app using flutter and firebase. I used stream builders to display questions and options from Firestore. I now want to compare the option selected by the user with the correct answer present in the firestore documents. So I am trying to read and store the answers from firestore into a list and then compare it to the selected answer. I don't want to use a streambuilder or futurebuilder because I am not going to display the data. I am unable to find a way to do this. How do I approach this problem?
My code:
class _OptionsWidgetState extends State<OptionsWidget> {
int selectedIndex = -1;
bool isSelected = false;
@override
Widget build(BuildContext context) {
return ListView.separated(
separatorBuilder: (BuildContext context, int index) {
return SizedBox(height: 15.sp);
},
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsets.symmetric(vertical: 25.h, horizontal: 50.w),
itemCount: 4,
itemBuilder: ((context, index) {
return GestureDetector(
onTap: () {
isSelected == false
? {
setState(
() {
isSelected = true;
selectedIndex = index;
},
),
//COMPARING THE CORRECT ANSWER(Index) WITH THE SELECTED INDEX. I AM USING A SAMPLE ANSWERS LIST .
//HERE I WANT TO USE THE LIST OF ANSWERS RETRIEVED FROM FIRESTORE.
if (selectedIndex == answers[widget.questionNumber])
Provider.of<TotalScore>(context, listen: false)
.updateScore(10),
print(Provider.of<TotalScore>(context, listen: false)
.totalScore),
}
: null;
},
child: Container(
...
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Options(
index: widget.questionNumber, optionindex: index)),
],
),
),
);
}),
);
}
}
final List<int> answers = [2,3,1,2,3,1,4,1,5];
My firestore database structure:
UPDATE:
Below code to show how I am fetching the questions.
I am using PageView to display question widgets:
class _Quiz1State extends State<Quiz1> {
final PageController _pageController = PageController(
initialPage: 0,
);
@override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _pageController,
children: [
QuestionWidget(
currentQuestion: 1,
pageController: _pageController,
totalQuestions: 10,
// question: 'Why is speed relative?',
question: const Question(index: 0),
index: 0,
),
QuestionWidget(
currentQuestion: 2,
pageController: _pageController,
totalQuestions: 10,
// question: 'How is universe expanding?',
question: const Question(index: 1),
index: 1,
),
.....
CheckScoreWidget(pageController: _pageController),
ResultsWidget(),
],
),
}
Using stream builder to fetch questions:
class Question extends StatelessWidget {
const Question({
Key? key,
required this.index,
}) : super(key: key);
final int index;
@override
Widget build(BuildContext context) {
final Stream<QuerySnapshot> _usersStream =
FirebaseFirestore.instance.collection('quiz1').snapshots();
return StreamBuilder<QuerySnapshot>(
stream: _usersStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot)
{
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: Text(" "),
);
}
List<QueryDocumentSnapshot<Object?>> data = snapshot.data!.docs;
return Text(
data[index]['title'],
style: GoogleFonts.poppins(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
);
},
);
}
}
Question Widget:
class QuestionWidget extends StatelessWidget {
const QuestionWidget({
Key? key,
required this.currentQuestion,
required this.pageController,
required this.question,
required this.totalQuestions,
required this.index,
}) : super(key: key);
final int currentQuestion;
final PageController pageController;
final Widget question;
final int totalQuestions;
final int index;
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
),
child: Column(
children: [
Stack(
alignment: Alignment.center,
children: [
Column(
children: [
Container(
decoration: BoxDecoration(
....
),
SizedBox(
height: 20.h,
),
OptionsWidget(questionNumber: index),
],
),
);
}
}
CodePudding user response:
First I would create a new class called QuestionModel (or something similar) to hold the data for a single question:
class QuestionModel {
final String title;
final String answer;
final List<String> options;
QuestionModel({
required this.title,
required this.answer,
required this.options,
});
}
Then I would move your firebase logic up a level to the Quiz1
class you have. This allows you to dynamically build the list of questions instead of having to manually do it. This also means you can probably straight up get rid of your Question
stateless widget:
class _Quiz1State extends State<Quiz1> {
late Stream<QuerySnapshot<Map<String, dynamic>>> _quiz;
@override
void initState() {
super.initState();
// Move to initState
_quiz = FirebaseFirestore.instance.collection('quiz1').snapshots();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<QuerySnapshot>(
stream: _quiz,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
return PageView(
physics: const NeverScrollableScrollPhysics(),
children: snapshot.data!.docs
// Here we map every document from firebase into a QuestionModel
// and pass the model class to the QuestionWidget
// We no longer have to manually track the indices of the questions
.map(
(question) => QuestionWidget(
question: QuestionModel(
title: question['title'],
answer: question['answer'],
options: question['options'],
),
),
)
.toList(),
);
},
),
);
}
}
I'd like to point out that the question[title]
and similar code will only work if the firebase data types match the QuestionModel types. If this were a production app, I would do some type checking to make sure everything is correct before I do anything with the data. Perhaps you could change the type of answer
to a Number in firebase and then assign it to an int in QuestionModel
. I'll leave that to you to decide. Anyway, now that we have our data in a List<QuestionModel>
we can pass that into a QuestionWidget
:
// This was modified to be very basic. Just pass the question in and display the fields
class QuestionWidget extends StatelessWidget {
const QuestionWidget({
Key? key,
required this.question,
}) : super(key: key);
final QuestionModel question;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
question.title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
question.answer.toString(),
style: TextStyle(
fontSize: 14,
),
),
...question.options.map((option) => Text(option)).toList(),
],
);
}
}
The map
function I keep using basically transforms a list of data into a corresponding list of widgets. It spits out an Iterable so we need to use toList()
.
Here is a question for you: How do you think you could modify the data model to eliminate the answer
field? Perhaps you could change options
to take in a new AnswerModel class like so:
class AnswerModel {
final String answer;
final bool isCorrect;
AnswerModel({
required this.answer,
required this.isCorrect,
});
}
Now each individual answer "knows" if it's the right answer or not, and you don't have to manually compare different fields. I'll leave this up to you to decide if that's right for you. Also note that I removed a lot of your code that wasn't necessary to answering your question, so I'll also leave that up to you to fit back in.