Let's Create a True-False Quiz Game App Using the Flutter
Hey All ๐,
It's been a week since the last post was busy with work but don't mind as I will still doing the Angela Yu course I am back with 2 more apps I have created while following the Course which I will tell about in this and My next post ๐.
Introduction
So In this post, we'll be creating a true or false quiz app we have named it Quiz_app
for simplicity. while creating this app we'll learn about these concepts in the flutter:
- Alert package
- FlatButton
- And Lot's of OOP concepts
Implementation
So let's begin right away. By the end, our app going to look like this:
Base setup
As you already know let's set up our base setup by running the flutter command flutter create Quiz_app
.
So next as we are not going to write any test cases just remove the test folder ๐. And Cleanup the main.dart
which contains the default app code.
You can get the whole code setup in this repo
Create True-False and Place for the Question
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
backgroundColor: Colors.grey.shade900,
body: Quizzler(),
),
),
);
}
class Quizzler extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: QuizPage(),
),
);
}
}
class QuizPage extends StatelessWidget {
Widget build(BuildContext context) {
return Container();
}
}
So As you can it is some large amount of code but just start making sense of the one line at a time.
first the main()
which will be called when the app is getting build then using the MaterialApp we are going to build the base of the app next we are using the scaffolding we are placing the all the widget inside of the screen and we are setting the background colour to grey so make it look better.
Next up the StatelessWidget
, only part of the code is not going to be changed. So inside this class, we are going to add the SafeArea
widget which prevents the widgets from the overflow outside of the screen. So that none of the information get lost out of the screen ๐
. Next, let's add some padding between all the widget we are going to add and the screen end to make them look better.
Next, we are creating the StatefulWidget
which changes upon user actions and naming the class as QuizPage
as it contains the code related to the Quiz display page. Inside this class, we are going to create the 2 button widgets and then a Text widget to display the question. To use this class let's add the Constructor to the child in the StatelessWidget class.
As we are going to add multiple widgets one below the other we need to use the Column
widget which helps us in displaying the multiple widgets in a single screen.
A widget that displays its children in a vertical array.
To cause a child to expand to fill the available vertical space, wrap the child in an Expanded widget.
The Column widget does not scroll (and in general it is considered an error to have more children in a Column than will fit in the available room). If you have a line of widgets and want them to be able to scroll if there is insufficient room, consider using a ListView.
class QuizPage extends StatelessWidget {
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Center(
child: Text(
'This is where question go',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
),
),
),
),
),
],
);
}
}
Next, Create the Text
widget which we are going to use to display the question. Create a Text widget which some text like this is where the question goes
and align the text to the centre using the textAlign
parameter. Next up to give some text style using the TextStyle
widget which helps us setting up the font style such as font size, font family etc. and also the colour in which text should be displayed (You can play around with colour and choose which you want to display it in).
class QuizPage extends StatelessWidget {
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Center(
child: Text(
'This is where question go',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: FlatButton(
textColor: Colors.white,
color: Colors.green,
onPressed: () {},
child: Text(
'True',
style: TextStyle(
color: Colors.white,
fontSize: 24.0,
),
),
),
),
),
],
);
}
}
Now it's Time to add the True Button using the FlatButton
widget. And let's make the Colour of the Button as Green which always symbolises the true. using the child parameter let's add text on the button which says True
so the user will know which button is true and which is false and some style to the text so button looks better.
Now it's time for you to add the false button and now our app looks like this:
Now it's not looking that much good with those large buttons so let's try to make the buttons smaller and give more space to the Question which can be done adding a single line which is flex
in Expanded
widget which tells how much space is given to which widget. (Just like the flex in CSS)
class QuizPage extends StatelessWidget {
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 6,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Center(
child: Text(
'This is where question go',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: FlatButton(
textColor: Colors.white,
color: Colors.green,
onPressed: () {},
child: Text(
'True',
style: TextStyle(
color: Colors.white,
fontSize: 24.0,
),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: FlatButton(
textColor: Colors.white,
color: Colors.red,
onPressed: () {},
child: Text(
'False',
style: TextStyle(
color: Colors.white,
fontSize: 24.0,
),
),
),
),
),
],
);
}
}
Now our app looks are done it's time to add the question and answers to the List and display them one by one.
Create a Question and QuizBrain class to hold the question
class Question {
String question;
bool answer;
Question(String question, bool answer) {
this.question = question;
this.answer = answer;
}
}
This class helps us accessing the one question and answer at time to display them on the screen.
Now it's time to add the brain to our app. which stores the questionnaire and method to access the question and answer so the user can't inter with the question and answer.
import 'package:quiz_app/Question.dart';
class QuizBrain {
List<Question> _questionBank = [
Question('Some cats are actually allergic to humans', true),
Question('You can lead a cow downstairs but not upstairs.', false),
Question('Approximately one-quarter of human bones are in the feet.', true),
];
String getQuestion(int questionNumber) {
return _questionBank[questionNumber].question;
}
bool getCorrectAnswer(int questionNumber) {
return _questionBank[questionNumber].answer;
}
}
So as you can see we have created the List of the Question which holds the question and answers and 2 methods which helps in getting the question and to check if the user answer is correct or not. Also, we are using the Quiz constructor to create the Question objects which holds the question and answer and object is the best way to store the multiple types of data.
Now it's time to add this to our app.
QuizBrain quizBrain = QuizBrain();
class _QuizPageState extends State<QuizPage> {
int questionNumber = 0;
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 6,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Center(
child: Text(
quizBrain.getQuestion(questionNumber),
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: FlatButton(
textColor: Colors.white,
color: Colors.green,
onPressed: () {
var correctAnswer = quizBrain.getCorrectAnswer(questionNumber);
if (correctAnswer == true) {
setState(() {
questionNumber++;
});
} else {
SetState(() {
questionNumber++;
});
}
},
child: Text(
'True',
style: TextStyle(
color: Colors.white,
fontSize: 24.0,
),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: FlatButton(
textColor: Colors.white,
color: Colors.red,
onPressed: () {
var correctAnswer = quizBrain.getCorrectAnswer(questionNumber);
if (correctAnswer == false) {
SetState(() {
questionNumber++;
});
} else {
SetState(() {
questionNumber++;
});
});
}
},
child: Text(
'False',
style: TextStyle(
color: Colors.white,
fontSize: 24.0,
),
),
),
),
),
],
);
}
}
So now let's create the object for the QuizBrain
for so that we can use the methods offered by the class since the List is defined as private we can't access that data directly so we are using the methods to get the data now in the app we have made several changes such we have added SetState so that when user click on any of the answers then the question number get updated and next question is displayed to the user. and Now just need to add the quizBrain.getQuestion(questionNumber)
to get the question from the quizBank
and display on the screen based on the question number and using the var correctAnswer = quizBrain.getCorrectAnswer(questionNumber);
we are taking the answer of that question and storing on a variable so we can check if the user clicks answer is correct or not.
Now let's add the Scoring System
Now as we have the quiz we also need some sort of scoring system so that user can know how many answers did he got right and how many wrong. So to do that let's use the right and cancel Icons offered by the Icon Widget.
To reduce the repetition of code we just create a method which checks if the users answer is right or wrong and update the scoring system and also increase the question number to get the next question.
void checkAnswer(bool userPickedAnswer) {
bool correctAnswer = quizBrain.getCorrectAnswer();
setState(() {
if (userPickedAnswer == correctAnswer) {
scoreKeeper.add(
Icon(
Icons.check,
color: Colors.green,
),
);
} else {
scoreKeeper.add(
Icon(
Icons.close,
color: Colors.red,
),
);
}
questionNumber ++;
});
}
As you can see we are taking the user click as a parameter and check with the correct answer then based on the result we are going to add the Icon to the scorekeeper list which is the List list of Icon type.
As you know Icon widget provides us with a lot the inbuilt icons to use now we just need to customise it a bit like adding the colour to them like check as green and close to red to symbolism.
Also, we are displaying these Icons by creating a Row widget at the end of the Icon widget which holds the Button and Text which displays the question and buttons. As we want to display the side of the icon by side we are using the Row
widget.
class _QuizPageState extends State<QuizPage> {
List<Icon> scoreKeeper = [];
void checkAnswer(bool userPickedAnswer) {
bool correctAnswer = quizBrain.getCorrectAnswer();
setState(() {
if (userPickedAnswer == correctAnswer) {
scoreKeeper.add(
Icon(
Icons.check,
color: Colors.green,
),
);
} else {
scoreKeeper.add(
Icon(
Icons.close,
color: Colors.red,
),
);
}
quizBrain.getNextQuestion();
});
}
}
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 6,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Center(
child: Text(
quizBrain.getQuestion(),
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: FlatButton(
textColor: Colors.white,
color: Colors.green,
onPressed: () {
checkAnswer(true);
},
child: Text(
'True',
style: TextStyle(
color: Colors.white,
fontSize: 24.0,
),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: FlatButton(
textColor: Colors.white,
color: Colors.red,
onPressed: () {
checkAnswer(false);
},
child: Text(
'False',
style: TextStyle(
color: Colors.white,
fontSize: 24.0,
),
),
),
),
),
Row(
children: scoreKeeper,
)
],
);
}
}
We passing the scoreKeeper List of Icon which is widget list which is required by the Row widget so we don't need any extra lines of code
Next, we need to check for the end question bank which results in error page as we are trying to access the data which Is not there so and also to make more the abstraction we are going to move the question number to the QuizBrain so we can add more to abstraction to our class.
import 'package:quiz_app/Question.dart';
class QuizBrain {
int _questionNumber = 0;
List<Question> _questionBank = [
Question('Some cats are actually allergic to humans', true),
Question('You can lead a cow downstairs but not upstairs.', false),
Question('Approximately one-quarter of human bones are in the feet.', true),
];
void getNextQuestion() {
if (_questionNumber < _questionBank.length - 1) {
_questionNumber++;
}
}
String getQuestion() {
return _questionBank[_questionNumber].question;
}
bool getCorrectAnswer() {
return _questionBank[_questionNumber].answer;
}
bool isFinished() {
if (_questionNumber >= _questionBank.length - 1)
return true;
else
return false;
}
void reset() {
_questionNumber = 0;
}
}
So now we have 3 more methods one to get the next question one to check if we are at the end of question bank and one to reset the question number to zero when we hit the end of the question bank.
So let's update the main.dart file a little to add these changes to our code
void checkAnswer(bool userPickedAnswer) {
bool correctAnswer = quizBrain.getCorrectAnswer();
if (quizBrain.isFinished()) {
quizBrain.reset();
scoreKeeper.clear();
} else {
setState(() {
if (userPickedAnswer == correctAnswer) {
scoreKeeper.add(
Icon(
Icons.check,
color: Colors.green,
),
);
} else {
scoreKeeper.add(
Icon(
Icons.close,
color: Colors.red,
),
);
}
quizBrain.getNextQuestion();
});
}
}
So we are now done with creating a quiz app which shows the user question with a true and false option based on user answer if the users answer was right we are going to display the score at bottom of the screen. Once we hit the end the scorekeeper is reset and the questions are again displayed from the beginning.
Let's add Alert to Show End of Quiz
So to add the alert we are going to import the rflutter_alert
from the pub.dev
which helps us in creating an alert message which we are going to display the congratulations at the end to the quiz. So by following the instruction let's just add the dependency in pubsec.yaml
file and import the package.
Then we just need to this line of code
if (quizBrain.isFinished()) {
Alert(
context: context,
title: "Quiz Score",
desc: "You've completed the quiz.")
.show();
quizBrain.reset();
scoreKeeper.clear();
}
So by this, we are going to get displayed with an alert like this:
Thank you
If you are still reading and got your own quiz app you just can add more question to the question bank and more customization to your app such as displaying the score in the alert and also based on the score you can display different type of alerts, etc.
Thanks for reading this lengthy post ๐ if you liked the post leave a like and comment that will give me some motivation to keep this up. Also, leave comment definitely if you find any mistakes so I can correct myself.
In next post, I will be back one more app which is almost similar to this app but it will be story-based quiz by mean depending on the user answer are going to display the next question ๐.
till then เค เคฒเคตเคฟเคฆเคพ ๐