MakeQuiz and TakeQuiz

What You're Building

MakeQuiz and TakeQuiz are two apps that, in tandem, allow a teacher to create quizzes for a student. Parents can create fun trivia apps for their children during a long road trip, grade school teachers can build "Math Blaster" quizzes, and college students can build quizzes to help their study groups prepare for a final. This tutorial will walk you through creating both the MakeQuiz and the TakeQuiz app.

This tutorial is a followup of the QuizMe tutorial-- if you haven't completed that tutorial you should do so before continuing.

Getting Started

Connect to the App Inventor web site and start a new project. Name it MakeQuiz, and also set the screen's Title to MakeQuiz. Open the Blocks Editor and connect to the phone.

Introduction

You'll design two apps, MakeQuiz for the "teacher" and TakeQuiz for the "student". With MakeQuiz:

  • The user enters questions and answers in an input form.
  • The previously entered question-answer pairs are displayed.
  • The quiz is stored persistently, in a database.

TakeQuiz will work similarly to the QuizMe app-- in fact you'll build it using QuizMe as a basis. TakeQuiz will differ in that the questions asked will be those that were entered into the database using MakeQuiz.

This tutorial introduces the following App Inventor concepts:

  • Input forms for allowing the user to enter information.
  • Displaying lists: serializing a list variable to display it on separate lines.
  • Persistent data: MakeQuiz will save the quiz questions and answers in a database and TakeQuiz will load them in from the database.
  • Data Sharing: the two apps together illustrate how two phones (and even two apps) can communicate through a shared, web database:

Set up the Components for MakeQuiz

Use the Component Designer to create the interface for MakeQuiz. When you finish, it should look something like the snapshot below (there are also more detailed instructions below the snapshot).

You'll use the following components to create MakeQuiz. Drag each component from the Palette into the Viewer and name it as specified below:

 Component Type
Palette Group
 What you'll name it
 Purpose of Component
HorizontalArrangement Screen Arrangement HorizontalArrangement1 Display the question prompt and texbox on one line
Label Basic Label1 The "Question:" prompt
TextBox Basic QuestionText User enters questions here
HorizontalArrangement Screen Arrangement HorizontalArrangement2 Display the answer prompt and textbox on one line
Label Basic Label2 The "Answer:" prompt
TextBox Basic
AnswerText User enters answers here
Button Basic
SubmitButton User clicks to submit a QA pair.
Label Basic QuestionsAnswersLabel This will display previously entered QAs
TinyWebDB Not Ready for Prime Time TinyWebDB1 to store to and retrieve from database

Set the properties of the components in the following way:

  • Set the text of Label1 to "Question": and the text of Label2 to "Answer":
  • Set the Hint of QuestionText to "enter a question" and the Hint of AnswerText to "enter an answer".
  • Set the Text of SubmitButton to "Submit"
  • Set the Text of QuestionsAnswersLabel to "Questions and Answers".

Put Label1 and QuestionText in HorizontalArrangement1, and Label2 and AnswerText in HorizontalArrangement2.

Add Behaviors to the Components

Open the Blocks Editor to add the behaviors for the app. As with the original QuizMe app, you'll first define some global variables for the QuestionList and AnswerList, but this time you won't provide fixed questions and answers. You'll need the following blocks:

 Block Type
Drawer
Purpose
def variable ("QuestionList")
Definitions Defines the QuestionList variable (rename it)
def variable ("AnswerList") Definitions Defines the AnswerList variable (rename it)
make a list Lists to set up the QuestionList for new items
make a list Lists
to set up the AnswerList for new items

The blocks should look like this:

Note that, unlike the original QuizMe app, the lists are empty to begin with. This is because with MakeQuiz and TakeQuiz, all data is created by the user of the app (it is user-generated).

Handle the User's entries

The first behavior you'll build is for handling the user's input. Specifically, when the user enters a question and answer and clicks submit, you'll use add item to list blocks to update the QuestionList and AnswerList.

You'll need the following blocks:

 Block Type
Drawer
Purpose
SubmitButton.Click
SubmitButton This event is triggered when the user clicks this button.
add items to list (2) Lists Use these to add the data entered by the user to the lists
global QuestionList My Definitions Plug into list slot of first add items to list
QuestionText.Text QuestionText
User's entry; plug it into item slot of first add items to list.
global AnswerList My Definitions Plug into list slot of second add items to list
AnswerText.Text AnswerText User's entry; plug it into item slot of second add items to list.

The blocks should look like this:

How the Blocks Work

add items to list appends each item to the end of a list. Here, the app takes the text the user has entered in the QuestionText and AnswerText text boxes and appends each to the corresponding list.

The behavior updates the hidden QuestionList and AnswerList variables, but the changes are not shown to the user. To display these lists, create a procedure "displayQAs" which makes a text object with both lists and places it into the QuestionsAnswersLabel. Then make sure to call the procedure at the bottom of SubmitButton.Click. You'll need the following blocks:

 Block Type
Drawer
Purpose
procedure "displayQAs" Definitions put the code to display questions and answers here
set QuestionsAnswersLabel.Text to QuestionsAnswersLabel display the lists in this label.
make text Text make a text object out of two lists and a colon separator
global QuestionList My Definitions plug into make text
text (":") Text plug into make text
global AnswerList My Definitions plug into make text
call displayQAs My Definitions place in the bottom of the SubmitButton.Click event-handler

The blocks should look like this:

How the Blocks Work

The displayQAs procedure is called after the lists are modified in SubmitButton.Click. The QuestionsAnswersLabel is modified to display the two lists separated by a colon. By default, App Inventor displays lists with surrounding parenthesis and spaces between items:

(item1 item2 item3)

Of course, this is not the ideal way to display the lists, but it will allow you to test your behavior for now. Later, you'll modify the displayQAs procedure to display a list on separate lines, and to display each question together with its corresponding answer.

Test this behavior. On the phone, enter a question and answer and click Submit. The app should display the single entry in the QuestionList, a colon, and then the single entry in the AnswerList. Add a second question and answer to make sure the lists are being created correctly.

Blanking Out the Question and Answer

When a user submits a question-answer pair, you should clear the QuestionText and AnswerText text boxes so they're ready for a new entry.

You'll need the following blocks:

 Block Type
Drawer
Purpose
set QuestionText.Text to QuestionText
To blank out question
set AnswerText.Text to AnswerText To blank out answer
text (2 blank ones)    

Here's how the blocks for the updated event handler should look:

How the Blocks Work

When the user submits a new question and answer, they are added to their respective lists. At that point, the text in the QuestionText and AnswerText is "blanked out" with empty text blocks (you create an empty text block by clicking on the the block's text and pressing the delete button).

Test the behavior. Add some questions and answers. When you submit them, the QuestionText and AnswerText should show only their hint (when a TextBox's Text property is empty it shows the hint until the user clicks it).

Storing the questions and answers in a database

As is, the app records the questions and answers entered by the user, but only in the phone's temporary memory. All variables in App Inventor are transient data-- they are stored on the phone and the data only "lives" through one run of an app. When the user closes the app, the data is lost.

The MakeQuiz app needs to record the questions and answers in a database. This will allow the quiz creator (teacher) to always edit the latest update of the quiz. It will also allow the TakeQuiz app to load the quiz and administer it to the student.

You'll use the TinyWebDB component to store and retrieve the QuestionList and AnswerList to and from a database. TinyWebDB allows you to store data in AppInventor-compliant databases that live on the web instead of on the phone.

The general scheme for storing a list persistently is the following: each time a new item is added to the list, use TinyWebDB to update the database version of the list. Then each time the app is opened (Screen1.Initialize event), reload the database version of the list into the variable.

Start by storing the QuestionList and AnswerList in the database each time the user enters a new pair. You'll need the following blocks:

 Block Type
Drawer
Purpose
TinyWebDB1.StoreValue TinyWebDB1
for storing questions in the database
text ("questions") Text Plug in "questions" as the tag of StoreValue
global QuestionList My Definitions plug into the value slot of StoreValue
TinyWebDB1.StoreValue TinyWebDB1 for storing answers in the database
text ("answers") Text Plug in "answers" as the tag of StoreValue
global AnswerList My Definitions plug into the value slot of StoreValue

The blocks should look like this:

How the Blocks Work

The last two rows of blocks store data in the database. The tag arguments label the data being stored so that you can retrieve it later. The QuestionList is stored with a tag of "questions" while the AnswerList is stored with a tag of "answers".

Although our example uses "questions" as the tag, you should actually use your own tag, e.g., "DavesQuestions", because by default, the database entries of all App Inventor programs share the same namespace and so your list of questions may get confused with someone else's list of questions since you both used the same tag. For more information, see Creating a Custom TinyWebDB service.

Test this behavior. TinyWebDB stores information on the web, so you can open a browser page to test whether this behavior is working. By default, TinyWebDB stores information at http://appinvtinywebdb.appspot.com. Browse there to check if your data was stored as desired. There will be many entries, as the default service is for testing and is used by many programmers. If you search on the page, you should find the tag-value pairs you just stored.

Because the default service is shared amongst programmers and apps, it is only for testing. Fortunately, setting up your own database service is straight forward. For more information, see Creating a Custom TinyWebDB service.

Loading Data from the Database

Once the blocks for storing the lists are working, you can add the blocks for loading the lists back in each time the app begins.

Loading data from a TinyWebDB database is a two-step process. First, you request the data by calling TinyWebDB.GetValue and providing a tag.

GetValue only requests the data from the web service. After the request is made, your app can do other things, such as responding to user actions, while it waits for the TinyWebDB web service to send the data. When the data arrives, a TinyWebDB.GotValue event is triggered. When that event occurs, the data requested is in a variable named "valueFromWebDB". The tag you requested is in the variable "tagFromWebDB". You can access these variables in the TinyWebDB.GotValue event handler.

In this case, the app needs to request two things from the TinyWebDB web service, the questions and the answers, so the Screen1.Initialize will make two calls to getValue. When the data arrives and the GotValue event-handler is triggered, the app should check the tag to see which request has arrived, and then load the corresponding list. Once the lists are both loaded, displayQAs can be called to display them.

You'll need the following blocks:

 Block Type
Drawer
Purpose
Screen1.Initialize Screen1
Event handler triggered when app begins
TinyWebDB.GetValue (2) TinyWebDB to request the stored QuestionList and AnswerList
text ("questions") Text use as the tag to retrieve QuestionList
text ("answers") Text use as the tag to retrieve AnswerList
TinyWebDB.GotValue TinyWebDB event handler triggered when data arrives
ifelse Control need to ask which GetValue request arrived
= block Math compare tagFromWebDB to "questions"
text ("questions") Text this is the tag that was used to store QuestionList
value TagFromWebDB My Definitions An argument of GotValue, specifies which request
set QuestionList to My Definitions if TagFromWebDB is "questions" this list will be set
set AnswerList to My Definitions if TagFromWebDB is not "questions" this list will be set
value ValueFromWebDB (2) My Definitions this holds the value returned from database
if Control check if both the lists are loaded before displaying
= block Math compare the lengths of the lists
length of list (2) Lists check if the length of the lists are the same
global QuestionList My Definitions plug into one of the length of list blocks
global AnswerList My Definitions plug into the other length of list block
call displayQAs My Definitions display the newly loaded questions and answers

The blocks should look like:

How the Blocks Work

When the app begins, the Screen1.Initialize event is triggered. The app calls TinyWebDB1.GetValue twice, once to request the stored QuestionList and once to request the stored AnswerList. After requesting the data, the app can handle other events while it waits for the web database to answer the request.

When the data arrives from the web database, the TinyWebDB1.GotValue event is triggered. Since two requests were made, it will be triggered twice, once for each list.

In the GotValue event-handler, the value returned is in the variable valueFromWebDB. The corresponding tag is in tagFromWebDB. The blocks first check to see which request has returned (even though the request for "questions" is made first in Screen1.Initialize, there is no guarantee that the QuestionList will arrive first). If the tag is "questions", the valueFromWebDB is put into the variable QuestionList. Otherwise (else) it is placed in the AnswerList.

At the bottom of GotValue, the app checks to see if both lists have been loaded-- one way to check is to see if the lengths of the lists are the same. If they are, displayQAs is called to display them on the phone.

Test this behavior. Select Restart Phone in the Blocks Editor. When the app initializes, it should display the previously entered questions and answers. If you close the app and restart again, the previous quiz should still appear.

The version of MakeQuiz you've created so far works: the user can create a quiz and it will be stored persistently in the database. If you can accept the fact that the quiz is displayed in an inelegant way, you can proceed to creating the sister app, TakeQuiz, which lets a user step through the quiz and answer the questions.

If you'd like to learn how to display list data in a more elegant fashion, step through the steps directly below.

Displaying a List on Multiple Lines

In the app you've built so far, the question and answer lists are displayed separately and with the default list display format, e.g.,

(question1 question2 question3): (answer1 answer2 answer3)

In this section, you'll modify the app so that the newly entered question-answer pairs are displayed in tandem, one on each line:

question1:answer1
question2:answer2
question3:answer3

Displaying the data in this way is non-trivial, so you'll start by first displaying a single list, the QuestionList, with each item on a separate line.

To display a list on separate lines, you must serialize it. This means to build a single text object with all the items of the list in it and a special character, '\n', between each item. A "\n" within a text object appears as a newline character when displayed.

For example, suppose the user has entered two questions, "What is the capital of California?" and "What is the capital of New York?" and these have succesfully been added to the QuestionList. Your displayQAs procedure should build a text object from QuestionList that looks like:
"\nWhat is the capital of California\nWhat is the capital of New York?"

When displayed on the phone, such text would appear as:

    What is the capital of California?
    What is the capital of New York?

All the changes will occur in the procedure displayQAs. You'll remove the blocks within that procedure, and use a foreach block to build the text by successively adding each question. The blocks within the foreach should add the current item (question) to the end of the text object (QuestionsAnswersLabel) you are building. You'll need the following blocks:
 Block Type
Drawer
Purpose
set QuestionsAnswersLabel.Text to QuestionsAnswersLabel Initialize the label to empty before repeatedly adding to it
text (blank) Text plug into set QuestionsAnswersLabel.Text to
foreach Control For each item in list, you'll concatenate it to the QuestionsAnswersLabel
name var already in foreach the current item of foreach; rename it to "question"
global QuestionList My Definitions plug this into "in list" slot of foreach
set QuestionsAnswersLabel.Text to QuestionsAnswersLabel Iteratively build the text
make text Text build a text object
QuestionsAnswersLabel.Text QuestionsAnswersLabel plug into make text
value question My Definitions plug into make text (make sure you've renamed foreach variable to "question" )
text ("\n") Text New-line character so following text on another line

The blocks should look like this:

How the Blocks Work

When the procedure is called, the QuestionsAnswersLabel.Text is first set to the empty text. This is in preparation for the foreach block that comes next, which will build the text in QuestionsAnswersLabel.Text incrementally.

The foreach block specifies that the blocks within it are to be executed once for every item in QuestionList. If there are three questions, then the inner blocks will be executed three times. Within the foreach, the current item being processed is named question.

On each iteration, QuestionsAnswersLabel.Text is modified. Each time it is set to its previous value, along with the newline character, "\n" and the current question being processed.

When the foreach completes, QuestionsAnswersLabel.Text will be text string with all the items separated by newline characters. As the app is processing each iteration of the foreach, the phone display doesn't change. But when it completes, the text in QuestionsAnswersLabel.Text is displayed on the phone and the items appear on separate lines.

Test the behavior. On the phone, add another question and answer and click Submit. Now, only the questions should be displayed and they should be displayed on separate lines.

Including the answer in DisplayQAs

The displayQAs procedure you've created ignores answers and only displays questions. In the next version of displayQAs, you'll display the data as question-answer pairs, e.g.,

    What is the capital of California?: Sacramento
    What is the capital of New York?: Albany

Modify displayQAs. Use an index variable to access each answer as the foreach block walks through the questions. You'll need the following blocks:

Block Type Drawer Purpose
def var ( "answer") Definitions
To temporarily store each answer
def var ( "answerIndex") Definitions To keep track of which answer (and question) you're on
text ("text") Text Initialize the variable answer to text.
number (1) Math Initialize the variable answerIndex to 1
set answerIndex to My Definitions Re-initialize answerIndex each time displayQAs called
number (1) Math to re-initialize answerIndex to 1
set answer to My Definitions set this variable each time in the foreach
select list item Lists select from the list AnswerList
global AnswerList My Definitions plug into list slot of select list item
global answerIndex My Definitions plug into index slot of select list item
set answerIndex to My Definitions In order to increment it each iteration through loop
global answerIndex My Definitions to increment answerIndex, add 1 to itself.
number (1) Math to increment answerIndex

The blocks should look like this:

How the Blocks Work

The foreach only allows you to iterate through one list. In this case, there are two lists and you need to step through and select each answer as you proceed through the questions. The strategy is to use an index variable, as was done with the currentQuestionIndex in the QuizMe tutorial.

For this behavior, the index is a position in the AnswerList, so its named answerIndex. The index is set to 1 before the foreach begins. Within the foreach, it is used to select the current answer from the AnswerList, and then it is incremented.

Test this behavior. On the phone, add some more question/answer pairs. The display should now show each question with its corresponding answer, and each question:answer pair on separate lines.

Final MakeQuiz App

Take Quiz

Building TakeQuiz is simpler; it can be built with a few modifications to the QuizMe app you completed earlier (if you have not completed that tutorial, do so now before continuing).

Begin by opening your QuizMe app, choosing SaveAs, and naming the new project TakeQuiz. This will leave your QuizMe app as is and allow you to use a copy of it as the basis for TakeQuiz.

This version of MakeQuiz/TakeQuiz does not handle images, so first remove the images from the TakeQuiz app:
  1. In the Component Designer, choose each image from the media palette and delete it. Also delete the Image1 component, which will remove all references to it from the Blocks Editor.
  2. In the Blocks Editor, drag the PictureList to the trash.

Since TakeQuiz will work with database data, drag a TinyWebDB component into the Component Designer viewer.

Now modify the blocks so that the quiz given to the user is the one from the database. First, since there are no fixed questions and answers, remove all of the actual question and answer text blocks from the make a list blocks within the QuestionList and AnswerList. The blocks should look like this:

Now modify your Screen1.Initialize so that it calls TinyWebDB.GetValue twice to load the lists. The blocks should look like this:

Finally, drag out a TinyWebDB.GotValue event-handler. This event-hanlder should look similar to the one used in MakeQuiz, but here you only want to show the first question and none of the answers. The blocks should look like this:

How the Blocks Work

When the app begins, Screen1.Initialize is triggered and the app requests the questions and the answers from the web database. When each of those requests arrives, the TinyWebDB.GotValue event-handler is triggered. The app asks which request has come in, using tagFromWebDB, and places the valueFromWebDB into the appropriate list. If it is the QuestionList being loaded, the first question is selected from QuestionList and displayed.

Test the behavior. Click Restart Phone App. Does the first question from the quiz you created with MakeQuiz appear? Can you take the quiz just as you did with QuizMe (except for the pictures)?

Final Program (Take Quiz)

Variations

Once you get MakeQuiz and TakeQuiz working, you might want to explore some variations. For example,

  • Incorporate images into the apps. The images will need to be URLs from the web, and the test creator will need to specify these URLs as a third item in the MakeQuiz form.
  • Allow the user to delete items from the questions and answers. You can let the user choose using the ListPicker component, and you can remove an item with the remove list item
  • Allow multiple quizzes to be created. You'll need to conceptualize the list of quizzes as a list of lists.

Review

Here are some of the ideas covered in this tutorial:
  • You can store data persistently in a web database with the TinyWebDB component.
  • You retrieve data from a TinyWebDB database by requesting it with GetValue. When the web database returns the data, the TinyWebDB.GotValue event is triggered. At that point, you can put the data in a list or process it in some way.
  • TinyWebDB data can be shared amongst multiple phones and apps.
Google is grateful to Professor David Wolber, CS Professor at The University of San Francisco, for developing this tutorial.