Learn to create an interactive quiz game using Flutter, integrating web APIs for dynamic content while practicing responsible internet use.
Welcome to an exciting project where you’ll create an interactive quiz game using Flutter! This project will not only reinforce your coding skills but also introduce you to the world of web APIs, emphasizing responsible internet use. Let’s dive into the world of quizzes and learn how to fetch dynamic content from the web.
In this mini-project, you’ll build a quiz game that fetches questions from an online API. This will help you understand how to integrate web data into your apps while practicing safe and ethical internet usage. By the end of this project, you’ll have a functional quiz game that you can customize and share with friends!
First, let’s set up a new Flutter project and prepare it for web integration.
Create a New Flutter Project:
Open your terminal or command prompt and run the following command to create a new Flutter project:
flutter create quiz_game
Navigate into the project directory:
cd quiz_game
Add the http
Package:
To fetch data from the web, we’ll use the http
package. Open the pubspec.yaml
file and add the following dependency:
dependencies:
flutter:
sdk: flutter
http: ^0.13.3
Save the file and run flutter pub get
to install the package.
Next, we’ll choose an API to fetch quiz questions. For this project, we’ll use the Open Trivia Database, a free API that provides trivia questions.
Accessing the API:
Visit the Open Trivia Database to explore the available endpoints. You don’t need an API key for this service, making it easy to get started.
To handle the data from the API, we’ll define Dart classes that represent quiz questions.
Define a Question Model:
Create a new file question.dart
in the lib
directory and define the following class:
class Question {
final String question;
final List<String> options;
final String correctAnswer;
Question({required this.question, required this.options, required this.correctAnswer});
factory Question.fromJson(Map<String, dynamic> json) {
List<String> options = List<String>.from(json['incorrect_answers']);
options.add(json['correct_answer']);
options.shuffle(); // Shuffle options for randomness
return Question(
question: json['question'],
options: options,
correctAnswer: json['correct_answer'],
);
}
}
Now, let’s write functions to fetch quiz questions from the API.
Fetch Questions:
In your main.dart
or a separate service file, add the following function:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'question.dart';
Future<List<Question>> fetchQuestions() async {
final response = await http.get(Uri.parse('https://opentdb.com/api.php?amount=10&type=multiple'));
if (response.statusCode == 200) {
final List<dynamic> data = json.decode(response.body)['results'];
return data.map((json) => Question.fromJson(json)).toList();
} else {
throw Exception('Failed to load questions');
}
}
This function sends a GET request to the API and parses the JSON response into a list of Question
objects.
Design a user-friendly interface to display questions and options.
Create the UI:
In main.dart
, set up the basic structure for your app:
import 'package:flutter/material.dart';
import 'question.dart';
import 'fetch_questions.dart'; // Assume this file contains the fetchQuestions function
void main() {
runApp(QuizApp());
}
class QuizApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Quiz Game',
theme: ThemeData(primarySwatch: Colors.blue),
home: QuizPage(),
);
}
}
class QuizPage extends StatefulWidget {
@override
_QuizPageState createState() => _QuizPageState();
}
class _QuizPageState extends State<QuizPage> {
late Future<List<Question>> futureQuestions;
@override
void initState() {
super.initState();
futureQuestions = fetchQuestions();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Quiz Game')),
body: FutureBuilder<List<Question>>(
future: futureQuestions,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No questions available'));
} else {
return QuizWidget(questions: snapshot.data!);
}
},
),
);
}
}
Implement logic to evaluate answers and keep score.
Evaluate Answers:
Create a widget to display each question and handle user input:
class QuizWidget extends StatefulWidget {
final List<Question> questions;
QuizWidget({required this.questions});
@override
_QuizWidgetState createState() => _QuizWidgetState();
}
class _QuizWidgetState extends State<QuizWidget> {
int currentQuestionIndex = 0;
int score = 0;
void checkAnswer(String selectedAnswer) {
if (selectedAnswer == widget.questions[currentQuestionIndex].correctAnswer) {
score++;
}
setState(() {
currentQuestionIndex++;
});
}
@override
Widget build(BuildContext context) {
if (currentQuestionIndex >= widget.questions.length) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Quiz Completed! Your score: $score', style: TextStyle(fontSize: 24)),
ElevatedButton(
onPressed: () {
setState(() {
currentQuestionIndex = 0;
score = 0;
});
},
child: Text('Restart Quiz'),
),
],
),
);
}
final question = widget.questions[currentQuestionIndex];
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(question.question, style: TextStyle(fontSize: 18)),
...question.options.map((option) => RadioListTile<String>(
title: Text(option),
value: option,
groupValue: null,
onChanged: (value) {
checkAnswer(value!);
},
)),
],
);
}
}
Show the final score and provide feedback to the player.
Display Feedback:
The QuizWidget
already includes a simple feedback mechanism. You can enhance it by adding more detailed feedback or animations.
Ensure that the quiz questions are appropriate and respectful.
Ethical Use:
Always review the content fetched from the API to ensure it aligns with your values and is suitable for all audiences. Avoid displaying sensitive or inappropriate information.
Here’s the complete code for your interactive quiz game:
// main.dart
import 'package:flutter/material.dart';
import 'question.dart';
import 'fetch_questions.dart';
void main() {
runApp(QuizApp());
}
class QuizApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Quiz Game',
theme: ThemeData(primarySwatch: Colors.blue),
home: QuizPage(),
);
}
}
class QuizPage extends StatefulWidget {
@override
_QuizPageState createState() => _QuizPageState();
}
class _QuizPageState extends State<QuizPage> {
late Future<List<Question>> futureQuestions;
@override
void initState() {
super.initState();
futureQuestions = fetchQuestions();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Quiz Game')),
body: FutureBuilder<List<Question>>(
future: futureQuestions,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No questions available'));
} else {
return QuizWidget(questions: snapshot.data!);
}
},
),
);
}
}
class QuizWidget extends StatefulWidget {
final List<Question> questions;
QuizWidget({required this.questions});
@override
_QuizWidgetState createState() => _QuizWidgetState();
}
class _QuizWidgetState extends State<QuizWidget> {
int currentQuestionIndex = 0;
int score = 0;
void checkAnswer(String selectedAnswer) {
if (selectedAnswer == widget.questions[currentQuestionIndex].correctAnswer) {
score++;
}
setState(() {
currentQuestionIndex++;
});
}
@override
Widget build(BuildContext context) {
if (currentQuestionIndex >= widget.questions.length) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Quiz Completed! Your score: $score', style: TextStyle(fontSize: 24)),
ElevatedButton(
onPressed: () {
setState(() {
currentQuestionIndex = 0;
score = 0;
});
},
child: Text('Restart Quiz'),
),
],
),
);
}
final question = widget.questions[currentQuestionIndex];
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(question.question, style: TextStyle(fontSize: 18)),
...question.options.map((option) => RadioListTile<String>(
title: Text(option),
value: option,
groupValue: null,
onChanged: (value) {
checkAnswer(value!);
},
)),
],
);
}
}
// question.dart
class Question {
final String question;
final List<String> options;
final String correctAnswer;
Question({required this.question, required this.options, required this.correctAnswer});
factory Question.fromJson(Map<String, dynamic> json) {
List<String> options = List<String>.from(json['incorrect_answers']);
options.add(json['correct_answer']);
options.shuffle(); // Shuffle options for randomness
return Question(
question: json['question'],
options: options,
correctAnswer: json['correct_answer'],
);
}
}
// fetch_questions.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'question.dart';
Future<List<Question>> fetchQuestions() async {
final response = await http.get(Uri.parse('https://opentdb.com/api.php?amount=10&type=multiple'));
if (response.statusCode == 200) {
final List<dynamic> data = json.decode(response.body)['results'];
return data.map((json) => Question.fromJson(json)).toList();
} else {
throw Exception('Failed to load questions');
}
}
Now that you’ve built the basic quiz game, try customizing it:
Here’s a visual representation of how your quiz app might look:
graph TD; A[Start Quiz] --> B{Fetch Questions}; B -->|Success| C[Display Question]; B -->|Error| D[Show Error Message]; C --> E{User Selects Answer}; E -->|Correct| F[Increment Score]; E -->|Incorrect| G[Next Question]; F --> G; G --> H{More Questions?}; H -->|Yes| C; H -->|No| I[Show Final Score]; I --> J[Restart Quiz];
Remember, with great power comes great responsibility. Always ensure the content you fetch and display is appropriate and respectful. Encourage ethical use of APIs and respect privacy and data protection guidelines.