CPSC 110-08: Computing on Mobile Phones
Spring 2011

Databases and Persistent Data

This tutorial teaches you how to share data with friends using App Inventor's TinyWebDb, a simple online (web) database. It builds on the information that was presented in the previous DB and Lists Tutorial

Introduction

Databases are used to organize and manage information. They are a form of long term memory because the data they contain persist beyond the running of a single application. Once data is put into a database, it remains there until it is deleted. This differs from short term memory, such as global variables, which persist only as long as your app is running.

In a previous tutorial we described TinyDB, App Inventor's on-phone database. TinyDb is useful for storing persistent data on the phone, where the data will remain available between runs of the app. When you restart your app after not using it for a while or after restarting your phone, the data the app saved in TinyDb will still be available.

Tags and Values

Recall that the TinyDb stores data in a tag-value scheme. The tag is used as a key by which the data can be retrieved. For example:

TagValue
OneThis is the first string
TwoThis is another string

To store a value in a TinyDb, you would use the StoreValue procedure, which takes two arguments, a tag and a value. The tag is always text, but the associated value can be a number, or a string, or even a list.

To retrieve a value from the Db, you use the GetValue function, which takes the tag as an argument and returns the associated value as its result. In this case we are retrieving the list of data associated with "Hamlin":

Setting up a TinyWebDb

By default, the TinyWebDB component stores data on the Google cloud on a server whose URL is http://appinvtinywebdb.appspot.com.

The problem with storing your data there is that it is accessible to all App Inventor developers. Other developers could overwrite your data.

Fortunately, it is relatively easy to create your own TinyWebDB database service using some code that Prof. Wolber of the University of San Francisco has created. You don't even need to know how to code-- you can just download some sample code and then upload it to the "cloud" with Google's App Engine. For instructions, see http://appinventorapi.com/program-an-api-python/.

For this tutorial we will use a server that I have already created. It's URL is http://ram8647-appinventortest.appspot.com. In the App Inventor Viewer, you will want to set the TinyWebDb's ServiceURL property to this address.

Shopping With Friends

Let's suppose you and your friends are going shopping at the mall and want to keep in touch and share your "finds" -- bargains, cool stuff, etc.

The ShoppingWithFriends app will use a TinyWebDb component to store finds that you and your friends post to the web. It will also use a TinyDb component to remember what you were shopping for the last time you used the app. For this example, we'll only store the name of the item and its price. The user interface will be based on the following design:

The UI contains a ListPicker component that is pre-set to a list of shopping itesm -- i.e., shoes, bags, blouses, etc. When you choose an item off the ListPicker, the app will look up that item on the DBWeb database and display a list of finds on the TextBoxDisplay.

There are also a couple of TextBox components where the user can enter the finds location (what store) and price. When the Report a Find button is clicked, the where and price information is set to the DbWeb database to be shared among your friends.

Globals

For this app we need several variables and constants. A constant is a global variable whose value does not change as the program executes. To distinguish them from variables, we given them UPPERCASE names. In this case we have two constants, both of which will be used to represent Db Tags:

The last two global variables, item and pricesList are used to temporarily store the current type of item being shopped for and the list of that type of item that was downloaded from the web db. Global variables are not persistent data.

Initializing The App

When the app starts up, we want to retrieve the current shopping item from DBPhone, the phone's TinyDb. Note on the left the logic we use for this task. We first retrieve the value associated with the ITEMNAME tag and assign that value to our global item variable.

Of course, this retrieval could fail -- i.e., there could be no such tag in DbPhone. This will certainly be the case the first time you run the app.

So after the retrieval we test the value that is returned. If its the empty string -- i.e., if the lookup failed -- we set the ListPicker's label to "Select a Shopping Item".

If the retrieval was successful, we set the ListPicker's label to the item name and we call the DBWeb.GetValue procedure to look up a list of those items on DbWeb.

Note how we construct the tag to be used here. We join the string stored in ITEMLISTTAG (i.e., "shopping-") and the item name (e.g. "shoes") to create a tag like "shopping-shoes". Using this strategy we can construct unique tags for the various shopping items.

Our Data Model

When using a database it's important to design a model for your data. For complex applications, this can get involved. But for this simple app we can use a simple list of lists model, where for each tag, say "shopping-shoes", we store a list of finds where each find is a list of locations (stores) and prices. So in general our data model will look like this:

[ [store1, price1], [store2, price2], ..., [storeN, priceN]]

A particular case might be something like:

[ [Macy's, 10.98], [Marshalls, 6.99], [tjmaxx, 15.99] ]

In this case we have a list containing three element where each element is a list containing two items.

Asynchronous Web Retrieval

Retrievals from TinyWebDB are said to by asynchronous. Literally, this means they are not synchronized, which means that you can't really tell how long it will take for the DBWeb.GetValue operation to take.

More precisely, in programming this means that the program will not wait for the DBWeb.GetValue operation to complete. The program will continue independently of that operation. Note that this is different from DBPhone.GetValue which is a synchronous operation -- i.e., the program will wait until it completes before performing the next operation.

App Inventor uses the GotValue handler to handle asynchronous retrievals from a TinyWebDb. This handler is invoked whenever App Inventor receives data from the DBWeb database.

Note that this handler has two arguments, the tagFromWebDb and the valueFromWebDB. If the program were making lots of different queries, you would want to test which tag is being retrieved and take different actions for different tags. But in this case all our retrievals are of the form "shopping-X" and we are going to take the same action in each case.

But we do want to test whether the retrieval of, say "shopping-bags", returned anything. If the tag you are retrieving is not stored in the DB, the value returned will be the empty string. So if the value returned is the empty string we set pricesList to an empty list and we report "Not Found".

Otherwise, recall from our data model that we are storing a list of lists for each shopping item. So the value returned from the database will be a list. We store it in pricesList and display it in the TextBoxDisplay.

Handling the List Picker

The ListPicker can be used to change the type of item being shopped for. In terms of user interface design we could also have used a TextBox to input the user's preference. But that would be the error prone alternative: The type is part of the tag used to store and retrieve database entries. Therefore it is better to use a pre-defined list of tags to reduce errors that result from mis-typing.

Whenever the user makes a selection from the list picker, we display the selection as the list picker's label, we store their choice under the ITEMNAME tag in DBPhone, the phone's database, and we retrieve the list of items of that type from DBWeb. Again, the DBWeb.GetValue tag is something like "shopping-shoes", where "shoes" is the choice that the user just selected.

Reporting a New Find

To report a new find the user would fill in the store name and the prices and click ButtonUpdate, which would lead to the following actions:

When the update button is clicked, the program creates a new list of the form "[where, price]" and adds it to the current pricesList. At this point pricesList could be an empty list or it could be a list of the finds that were previously download from the DBWeb. Se we are adding our new find to the current list of finds.

After updating the pricesList, we want to store on the web database with the command DBWeb.StoreValue. Note again how we construct the tag here by joining "shopping-" with the type of item to get something like "shopping-belts".

Finally, we want to display the newly updated list, including any finds that may have been added by our friends. So we also call the DBWeb.GetValue procedure. Its result, remember, will be retrieved by the DBWeb.GotValue event handler.

Exercises

  1. The lists being displayed on the phone are not nicely formatted. Here's a function called makeFormattedStringFromList (given in the source code but not called) that prettifies the output:

    For the list "[[macys, 10.99], [marshalls, 9.95]]" this code will produce something like:

    shoes Found:
    [macys, 10.99]
    [marshalls, 9.99]
    

    Modify the app to use this function.

  2. As a further prettification of the output, modify the makeFormattedStringFromList function to produce the following output given the above list:
    shoes Found:
    macys: 10.99
    marshalls: 9.99
    
  3. Modify the app so that it stores your name as well as the store and price. So that a new model would be something like:
    [ [mary, macys, 10.99], [joe, marshalls, 9.99], [sue, tjmaxx, 8.99] ]
    

    The Sourcecode

    Here's the app's sourcecode.