Specialized development kit for building standard quiz apps. Built with kotlin
and lots of ☕
Making questions for a quiz app should be one of the least daunting tasks, but most times it isn't!
There's a difference between setting quiz questions and programming quiz questions. The aim of this library is to bridge the gap between setting actual quiz questions and building a quiz app. Questions can be set by simply creating a text file containing the questions written as though it was supposed to be a hand written quiz 😄. The file is then converted by the API into a complete Quiz
object used by the app. The app can thus be built in a very dynamic way by allowing the quiz to be created either from a file in phone storage, an input stream, a String, or from the internet.
The API is built to be both simple to use and highly customizable for developers. Inspired by flutter’s Widgets, a component based design is used allowing new quiz components to be designed and included to the app to change specific behaviors without interfering with the base implementations of the API. P.S: For lovers of flutter, a Dart version of the API is in production and will soon be made available.
View demo app and source code for usage.
This library was used in building the Uniport GES 300 Quiz App.
Other notable mentions include:
Built something great with this library? feel free to let us know 👍
- Introduction
- Demo
- How to Use
- The Quiz API
- The Question Object
- Quiz Components
- Contributing
- License
- Contact
The project can be pulled from jitpack using the required release.
In your root/project level gradle file add the following dependency
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
In your app level gradle file add the following dependency
dependencies {
implementation 'com.github.gesos:android-quiz-kit:v0.0.3-beta'
}
For the simple case of multiple choice questions (Interpreted by the SimpleQuizParser
class. For other question formats, see Setting a QuizParser), create a file containing the questions written in the following format.
- Add the
<!QUIZ>
header at the beginning of the file. This is used to verify that the contents of the file are quiz questions. To disable this feature and remove the header see Disabling verification). - New line characters (paragraphs) are used to determine the begining and end of a question or option. Place each question and individual option on a new line.
- For readability, place identifiers like
1.
,2.
,3.
,... at the begining of each question anda.
,b.
,c.
... at the begining of each option. The identifiers are ignored when reading the file, as such they can actually be ommitted, but for readability they are recommended to be placed. If you want to show identifiers while displaying the questions in the app, you would have to append it manually while updating the UI see Displaying questions). - Each question should have the same number of options. The default number of options is 4(four). If the number of options for each questions is less or greater than 4 see Specifying number of options).
- Place asterisks
*
at the begining of the option with the correct answer.To specify a different answer marker see Specifying answer marker).
<!QUIZ>
1. What programming language was this library written with?
a. javascript
b. python
*c. kotlin
d. c++
2. What is the aim of this library?
a. to drink coffee
*b. to bridge the gap between setting actual quiz questions and building a quiz app
c. to have fun
d. none of the above
To get the quiz you first need to create a QuizBuilder
, assigning it a BuildMethod
and a topic
There are five BuildMethods provided by the API
- Building from string
val method : BuildMethod = BuildMethod.fromString(quizString)
- Building from InputStream
val method : BuildMethod = BuildMethod.fromInputStream(quizStream)
- Building from Android Raw Resources
val method : BuildMethod = BuildMethod.fromResource(quizRes)
- Building from a File
val method : BuildMethod = BuildMethod.fromFile(quizFile)
Building from a urlNot yet implemented. Watch out for next release.
val method : BuildMethod = BuildMethod.fromUrl(quizUrl)
val builder : QuizBuilder = QuizBuilder.Builder(context).build("topic", method)
The Quiz
object is built asyncronously (any form of progress indicator can be used during this short time). Call getQuiz()
on the QuizBuilder to retreive the quiz once it is done building. getQuiz()
takes two arguments; a Quiz.Config
object for setting quiz configurations and a Quiz.OnBuildListener
to retreive the Quiz.
builder.getQuiz(Quiz.Config(), object : Quiz.OnBuildListener {
override fun onFinishBuild(quiz: Quiz) {
// Initialise the quiz UI here, and cancel any progress indicators
}
})
The Quiz.Config
object holds basic configurations for the quiz. Its public properties and their default values include the following:
// Randomizes the options for a question
var randomizeOptions: Boolean = false
// Randomizes the questions
var randomizeQuestions: Boolean = true
// Sets the number of questions to be retreived for the quiz. A value of -1 retreives all the questions
var questionCount: Int = -1
A Questions
object is returned from a call to any getQuestion()
method. The quiz tracks which question is currently being displayed by using the currentIndex
property. The Quiz
class implements the QuizController
interface. The following are some public methods provided by the Quiz
object.
The interface below shows the most useful quiz controller methods.
// returns the total number of questions retreived for the quiz
fun getQuestionCount(): Int
// gets the current question tracking index of the quiz
fun getCurrentQuestionIndex(): Int
// returns the question at current index
fun getCurrentQuestion(): Question
// increments the index by 1 and retreives the question
fun nextQuestion(): Question
// decreases the index by 1 and retreives the question
fun previousQuestion(): Question
// stores the selected answer for the current question
fun setSelection(selection: Int?)
// gets the selected answer for the current question
fun getSelection(): Int?
// gets the result of the selection for the current question. 1 for correct, 0 for wrong, -1 for unselected
fun getResult(): Int
The Question
object contains the information for a particular question. It includes the question statement, the options, the correct answer and an identifying key.
// retreiving the question
val question: Question = quiz.getCurrentQuestion()
// getting the question's properties
val statement: String = question.statement
val options: List<String> = question.options
val answerIndex: Int = question.answer
// setting up your UI
statementTextView.text = statement
optionsList.adapter = createAdapter(options)
val correctOption: String = options[answerIndex]
The Quiz object is designed to handle the selection made for a particular question. To answer a question first display the options on the UI. Listen for user selection. Oce selected get, get the index corresponding to that option and make a call to setSelection()
.
// sample for using an AdapterView.OnItemClickListener for a ListView to listen for user selection
optionsList.onItemClickListener = AdapterView.OnItemClickListener { _, _, optionIndex, _ ->
quiz.setSelection(optionIndex)
}
Components are designed to perform additional tasks like creating a history, implementing a timer, or some other specific functionalities. The following componenets are made available in the library
Saving history is implemented using the QuizHistory
class. Being able to persist user state and data is an important feature for any app. Using this componenet, a quiz can be paused and resumed from where the user last stopped (like a Quiz Game), simply persisting data in a bundle acrross the Activity's Lifecycle or even letting the user review his performance on a previously completed quiz.
To save a completed or uncompleted quiz to history, get an instance of QuizHistory, and call saveToHistory()
. Each quiz is associated with a specific id
or timestamp (a Long
number)which represents the time the quiz started. This id is used in saving to history and should also be used in retreiving the history.
QuizHistory.getInstance(context).saveToHistory(quiz)
// pass a Boolean with value of true as a second argument to save an uncompleted quiz
QuizHistory.getInstance(context).saveToHistory(quiz, true)
To persist data in a bundle acrross the Activity's Lifecycle, call the following method in the Activity's onSavedInstanceState()
callback
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
QuizHistory.saveToBundle(quiz, outState)
}
Resoring of state is done either from bundle or history. The bundle is first checked for any saved state before checking the history using the unique quiz id supplied to it if any.
QuizHistory.restoreState(quiz, savedInstanceState)
// pass an Long containing the quiz id as a third argument to review a completed quiz
QuizHistory.restoreState(quiz, savedInstanceState, quizId)
// pass a Boolean with value of true as a fourth argument to restore an uncompleted quiz
QuizHistory.restoreState(quiz, savedInstanceState, quizId, true)
The HistoryComponenet
is an interface that can be extended for the purpose of saving history with the QuizHistory
class. Classes that extend it needs access to a Quiz
object. These classes can also be passesed as a parameter where a Quiz
object is reequired in QuizHistory
methods.
This is a utility class of the QuizHistory
class. Stats are a way of displaying user performance on different quiz sessions. To get all Stats for completed quizes use:
// returns a list of stats
val allStats: List<QuizHistory.Stats> = QuizHistory.getInstance(this).getAllStats()
// returns the stats for a particular quizid
val allStats: QuizHistory.Stats = QuizHistory.getInstance(this).getStat(quizId)
The following are the information included in the Stats
object
// The topic assigned to the quiz
var topic: String
// the timestamp of the quiz same as the quiz id
var quizTimestamp: Long
// the index of attempted questions
var answeredIndexes: List<Int>?
// the number of attempted questions
var answeredCount: Int
// the indexes of correct answers
var correctIndexes: List<Int>?
// the number of correct answere
var correctCount: Int
// the number of questions set for the quiz
var questionCount: Int
A possible use case includes showing a recent history of stats, and letting the user review his performance on that session by clicking on a Stat, whose timestamp (quiz id) will then be passed in an intent to another activity. The timestamp is then used to rebuild the finished quiz session from history. see demo source code for example.
To make things a little more interesting and challenging a timer can be included into the quiz. The library includes a QuizTimer
class for this functionality. Build the timer by passing the Quiz and the total time for the quiz to its public constructor. The QuizTimer
implements the HistoryComponent
and QuizController
(Delegated to the quiz object received) interfaces, and as such can be passed as an argument to QuizHistory
in place of a Quiz
Object. Public QuizController
methods such as getCurrentQuestion()
can also be called from this class.
// pass in the quiz object and the total time for the quiz
val quizTimer: QuizTimer = QuizTimer(quiz, 10000)
// restore both the quiz history and the timer history
QuizHistory.restoreState(quizTimer, savedInstanceState)
// get question
val question: Question = quizTimer.getCurrentQuestion()
// ...
// Start the timer
quizTimer.start()
Listen for changes in time if you want to update a TextView, or end the quiz once its done. Do so by including an OnTimeChangeListener
to the timer.
quizTimer.onTimeChangeListener = object : QuizTimer.OnTimeChangeListener {
override fun onTimerTick(timeLeft: Long) {
// update time TextView
}
override fun onTimerFinish() {
finishQuiz()
}
}
The timer might need to be suspended, paused, finished or cancelled at diferent points in time, and then started at other points in time.
- Suspending the timer: Here the timer is not entirely stopped. It stops observing the changes in time, but records the value of the last time it observed from the realClockTime of the device. When the timer is then started at a later point, it calculates how much time has passed since it was suspended and removes it from the remaining time. Can be usedin Activity's callback like
onPause()
when the UI mignt not currently be visible to the user.
override fun onPause() {
super.onPause()
quizTimer.suspend()
}
- Pausing the timer: Here the timer is entirely stoped and started from the same point on the next start. Call this when you want to pause an unfinished quiz before saving to history.
fun pauseQuiz() {
quizTimer.pause()
// pass a Boolean with value of true as a second argument to save an uncompleted quiz
QuizHistory.getInstance(context).saveToHistory(quizTimer, true)
//...
}
- Finishing the timer: If the user finishes the quiz before the timer ends, this method should be used to stop the timer.
fun finishQuiz() {
quizTimer.finish()
QuizHistory.getInstance(context).saveToHistory(quizTimer)
//...
}
- Cancelling the timer: When the quiz is cancelled (perhaps by the user) this method should be used to properly stop the timer.
fun cancelQuiz() {
quizTimer.cancel()
//...
}
- Resuming the timer: Whatever the method used in stopping the timer, the same method is used each time in starting it. It can be used to begin the timer when it is first initialized or resume it after is has been paused in the activit's
onResume()
callback (a null safe check might be necessary before starting the timer inonResume()
as the variable might not have been initialised on Activity's creation due to its dependency on theQuiz
object).
// nullable quiz timer
var quizTimer: QuizTimer? = null
// Might be called at a later time after onResume() has already been called
fun initializeTimer(quiz: Quiz) {
val timer = QuizTimer(quiz, 10000)
//...
quizTimer = timer
timer.start()
}
override fun onResume() {
super.onResume()
// Null safe check
quizTimer?.start()
}
The QuizTimer
also provides an extension of QuizHistory.Stats
called TimedStats
. It includes two more stats; the quiz totalTime and the user's finishTime. Get the Timed stats by calling QuizTimer.getStat()
val stats: TimedStats? = QuizTimer.getStat(context, quizId)
val finishTime = stats.finishTime
val totalTime = stats.TotalTime
Contributions are open for this project. A Contribution and Issues guideline will soon be created for this purpose. Till then feel free to fork, contribute and post issues.
This project is licensed under the Apache License Version 2.0, January 2004. Read the license statement for more information.
- Support: [email protected]
- Developer: [email protected]
- Website: https://orsteg.com