'Navigate to next question or item in quiz app in a flutter list
I am creating a quiz app in dart/flutter and I want to automatically navigate to the next question when an answer is selected. The questions and answers are in a list. Please someone should point me where I am doing it wrong. Here is my code
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/quiz/question_provider.dart';
import '../providers/quiz/quiz_provider.dart';
import '../widgets/quiz/question_item.dart';
class QuestionScreen extends StatefulWidget {
static var quizId;
static const routeName = '/question-screen';
@override
State<QuestionScreen> createState() => _QuestionScreenState();
}
class _QuestionScreenState extends State<QuestionScreen> {
var _isInit = true;
var _isLoading = false;
@override
void didChangeDependencies() {
if (_isInit) {
setState(() {
_isLoading = true;
});
Provider.of<Question>(context).fetchQuestions().then((_) {
setState(() {
_isLoading = false;
});
});
}
_isInit = false;
super.didChangeDependencies();
}
// var _questionIndex = 0;
// void answerQuestion() {
// setState(() {
// _questionIndex = _questionIndex + 1;
// });
// }
@override
Widget build(BuildContext context) {
QuestionScreen.quizId = ModalRoute.of(context)!.settings.arguments as String;
final quiz = Provider.of<Quiz>(context).findById(QuestionScreen.quizId);
final question = Provider.of<Question>(context);
return Scaffold(
appBar: AppBar(
title: Text('${quiz.title}'),
),
body: Container(
padding: EdgeInsets.all(10),
child: ListView.builder(
itemCount: 1,
itemBuilder: (ctx, index) => ChangeNotifierProvider.value(
value: question.questions[index],
child: question.questions.isEmpty ? Container() : QuestionItem(),
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '/providers/quiz/question_provider.dart';
import '/screens/question_answers.dart';
import './question.dart';
import './answer.dart';
class QuestionItem extends StatefulWidget {
final Function? answerHandler;
const QuestionItem({Key? key, this.answerHandler}) : super(key: key);
@override
State<QuestionItem> createState() => _QuestionItemState();
}
class _QuestionItemState extends State<QuestionItem> {
@override
Widget build(BuildContext context) {
final loadedQuestions = Provider.of<Question>(context);
var questionIndex = 1;
void answerQuestion() {
setState(() {
questionIndex = questionIndex! + 1;
});
}
return GridTile(
child: GestureDetector(
onTap: () =>
Navigator.of(context).pushNamed(QuestionsAndAnswers.routeName),
child: Column(
children: [
QuestionText(
questionText: '${loadedQuestions.questionTitle!}'),
...(loadedQuestions.options as List<dynamic>).map((option) {
return Answer(
answerText: '${option}',
selectHandler: answerQuestion,
);
}).toList()
],
),
),
);
}
}
And below is the provider class where I fetch the quiz data from an API and add them to a list.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:firebase_auth/firebase_auth.dart';
import '/screens/question_screen.dart';
class Question with ChangeNotifier {
final String? id;
final String? questionTitle;
final String? description;
final String? lessonId;
final String? courseId;
final String? quizId;
final String? media;
final List<dynamic>? rightAnswers;
final List<dynamic>? options;
final int? grade = 1;
int? index = 0;
Question({
@required this.id,
@required this.questionTitle,
@required this.description,
@required this.lessonId,
@required this.courseId,
@required this.quizId,
@required this.media,
@required this.rightAnswers,
@required this.options,
@required this.index,
});
List<Question> _questions = [];
List<Question> get questions {
return [..._questions];
}
Question findById(String id) {
return _questions.firstWhere((question) => question.id == id);
}
Future<void> fetchQuestions() async {
try {
final String idToken =
await FirebaseAuth.instance.currentUser!.getIdToken();
final reauthUrl = Uri.parse(
'https://example.net/api/v1/User/reauth');
final reauthResponse = await http.post(reauthUrl,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: json.encode({"token": idToken}));
final returnedTokenData =
json.decode(reauthResponse.body) as Map<String, dynamic>;
final serverToken = returnedTokenData['token'];
final url = Uri.parse(
'https://example.net/api/v1/Quiz/Questions/${QuestionScreen.quizId}');
final response = await http.get(
url,
headers: {'Authorization': 'Bearer $serverToken'},
);
final responseData = json.decode(response.body);
print('ResponseData is $responseData');
final List<Question> loadedQuestions = [];
for (var question in responseData) {
loadedQuestions.add(
Question(
id: question['id'],
questionTitle: question['title'],
description: question['description'],
lessonId: question['lessonId'],
courseId: question['courseId'],
quizId: question['quizId'],
media: question['media'],
rightAnswers: question['rightAnswers'],
options: question['options'],
),
);
}
_questions = loadedQuestions;
print('These are the questions $_questions');
notifyListeners();
} catch (error) {
rethrow;
}
}
}
Solution 1:[1]
I finally figured out a way to navigate to the next question on the quiz.
I moved the questionIndex variable and answerQuestion() function from the QuestionItem class to the QuestionScreen page and passed the function as a parameter in the QuestionItem widget.
I then used question.questions[questionIndex] as the value of ListView.builder(), and voila! It works!
Here is the final code
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/quiz/question_provider.dart';
import '../providers/quiz/quiz_provider.dart';
import '../widgets/quiz/question_item.dart';
class QuestionScreen extends StatefulWidget {
static var quizId;
static const routeName = '/question-screen';
@override
State<QuestionScreen> createState() => _QuestionScreenState();
}
class _QuestionScreenState extends State<QuestionScreen> {
var _isInit = true;
var _isLoading = false;
var questionIndex = 0;
@override
void didChangeDependencies() {
if (_isInit) {
setState(() {
_isLoading = true;
});
Provider.of<Question>(context).fetchQuestions().then((_) {
setState(() {
_isLoading = false;
});
});
}
_isInit = false;
super.didChangeDependencies();
}
void answerQuestion() {
setState(() {
questionIndex = questionIndex + 1;
});
}
@override
Widget build(BuildContext context) {
QuestionScreen.quizId = ModalRoute.of(context)!.settings.arguments as String;
final quiz = Provider.of<Quiz>(context).findById(QuestionScreen.quizId);
final question = Provider.of<Question>(context);
return Scaffold(
appBar: AppBar(
title: Text('${quiz.title}'),
),
body: Container(
padding: EdgeInsets.all(10),
child: ListView.builder(
itemCount: 1,
itemBuilder: (ctx, index) => ChangeNotifierProvider.value(
value: question.questions[questionIndex],
child: question.questions.isEmpty ? Container() : QuestionItem(answerHandler: answerQuestion),
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '/providers/quiz/question_provider.dart';
import '/screens/question_answers.dart';
import './question.dart';
import './answer.dart';
class QuestionItem extends StatefulWidget {
final VoidCallback answerHandler;
const QuestionItem({Key? key, required this.answerHandler}) : super(key: key);
@override
State<QuestionItem> createState() => _QuestionItemState();
}
class _QuestionItemState extends State<QuestionItem> {
@override
Widget build(BuildContext context) {
final loadedQuestions = Provider.of<Question>(context);
return GridTile(
child: GestureDetector(
onTap: () =>
Navigator.of(context).pushNamed(QuestionsAndAnswers.routeName),
child: Column(
children: [
QuestionText(questionText: '${loadedQuestions.questionTitle!}'),
SizedBox(height: 10,),
...(loadedQuestions.options as List<dynamic>).map((option) {
return Answer(
answerText: '${option}',
selectHandler: widget.answerHandler,
);
}).toList(),
],
),
),
);
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Cliff |
