Welcome to part III of the Sharing Data With Gestures series. In part II, we created our intermediary server process in Ruby on Rails. This server process will act as a conduit between two devices that are trying to communicate with a gesture we are calling a "thump." When two devices are "thump"-ed together, the server will match them by calculating their proximity to each other through GPS coordinates as well as a near identical timestamp of when they opened up communication with the server. Once this match has been made, the server will exchange the messages typed in the mobile app, simulating the device-to-device communication.
In part III, we will deploy our server app to the heroku platform and then upgrade our mobile app to communicate with it.
To start, we will modify our thumps table migration file to be compatible with Postgres. You can find this file in the "db/migrate" directory and it will be named something like: TIMESTAMP_create_thumps.rb. The easiest way to deploy a heroku app is to use their shared database service, which happens to be Postgres instead of MySQL. We are going to replace the following lines:
t.decimal :lat, :precision=>8, :scale=>8 t.decimal :lng, :precision=>8, :scale=>8
with these new lines:
t.decimal :lat, :scale=>8 t.decimal :lng, :scale=>8
Postgres handles big decimal fields differently than MySQL does, so this is a necessary change to ensure we get the datapoint precision we need in our latitude and longitude fields.
Since this is a Rails 2.3.5 app, we will utilize the older heroku method of installing gems by creating a .gems file in the root of our Rails project. Most of you will probably be used to using Bundler for this kind of task, however since the geokit plugin has not been upgraded to be Rails 3.0 compatible, we need to do things with the older Rails 2 conventions.
We simply add the following to our .gems file:
rails -v 2.3.5 pg geokit --version '=1.5.0'
Here we specify the rails gem and version we are using for this project, as well as the postgres gem, and version 1.5.0 of the geokit gem.
Now we can begin our deployment! Let's start by creating a local git repository inside our project. All we have to do is run the following command at the root directory of our Rails project:
$ git init
Before we commit to this repository, we need create our app on the heroku platform. If you haven't signed up for a free heroku account yet, do so by simply registering at https://api.heroku.com/signup. If you don't have the heroku gem installed on your system yet, you can do so by running the following command:
$ sudo gem install heroku
Once the gem is installed, run the following command from inside the root directory of the project:
$ heroku create --stack bamboo-ree-1.8.7
Here we are specifying the bamboom-ree stack on account of this being a Rails 2 app. In the event that you have just created a new account, it may prompt you for your heroku account credentials. Once entered, these credentials will be stored for future interactions with the heroku servers. If all goes well, heroku should respond with something like the following:
Created http://APPNAME.heroku.com/ | [email protected]:APPNAME.git Git remote heroku added
Here I have replaced the actual application name and subdomain with a place holder called APPNAME. Take note of this APPNAME as we will be using it later on. Now we are going to commit our project files to the local git repository we created earlier. Its as simple as running these two commands:
$ git add . $ git commit -m "my first commit"
Once the project has been fully committed to the local git repo, we need to push it to the remote repository that was created when we ran the heroku create command.
$ git push heroku master
The heroku gem allows you to execute remote rake commands on the server with the command "heroku rake". To complete the deployment, we have to run our database migrations that will generate our thumps table in the heroku database.
$ heroku rake db:migrate
Congrats! You have successfully deployed our thump server application to the heroku platform. Now back to the mobile app!
Let's open up our main.lua file in our Corona app and add the following lines to the top:
http = require("socket.http") ltn12 = require("ltn12") url = require("socket.url") require("Json")
We need to require some libraries that will allow us to create a socket connection to our server app. We also will include the JSON parsing library so we can understand the response objects the server will send back.
Remember the APPNAME that was given when we first created the heroku app? It's time to use that now:
local appname = "APPNAMEHERE"
This variable will be combined with others later to generate our server URL for external communication.
In Part I, we had the app showing an alert box with our message when it detected a "thump" or shake gesture. Since we need to communicate this message to the server, we'll remove the following line from inside our getThump function:
local alert = native.showAlert( "Thump!", "Location: "..latitudeText..","..longitudeText.."\r\nMessage: "..textField.text, {"OK"} )
Now we are going to add some functionality to this getThump method to send our message to the server. Let's break this down:
local message = textField.text local post = "thump[deviceid]="..deviceId.."&thump[lat]="..latitudeText.."&thump[lng]="..longitudeText.."&thump[message]="..message local response = {}
Here we are generating our variables to send to the server. The "post" variable is setup in the query string format and our "response" is declared as an empty table object for now.
local r, c, h = http.request { url = "http://"..appname..".heroku.com/thumps", method = "POST", headers = { ["content-length"] = #post, ["Content-Type"] = "application/x-www-form-urlencoded" }, source = ltn12.source.string(post), sink = ltn12.sink.table(response) } local jsonpost = Json.Decode(table.concat(response,''))
Here we are executing an HTTP request of type POST to our server. We are subbing in our appname variable as the subdomain for the url. The headers are standard for a typical post call. In the "content-length" field, the lua syntax of putting a # in front of the variable will output the length in characters of that string. Since we want to store our server's repsonse in a variable called "response", our last line will decode that variable as a JSON object and create a lua table object so we can access fields within it.
In the event of a communication error, we need to alert the user that something has gone wrong. We'll create a generic showError() method to display an alert box to the user if this occurs:
local function showError() local alert = native.showAlert( "Error!", "Please try your thump again!", {"OK"} ) end
Due to the fact that Rails is single threaded by nature and since heroku free accounts allow you to run only a single server process; once the mobile app completes the POST call to send data, we are going to poll our server, asking for a response object. While this may not be the most ideal way to architect this, it allows us to run this type of app with very minimal heroku server resources.
Here is our polling logic below:
if(jsonpost.success == true) then native.setActivityIndicator( true ); local attempts = 0 function retrieveThump( event ) if 10 == attempts then native.setActivityIndicator( false ); timer.cancel( event.source ) showError() else local response = {} local r, c, h = http.request { url = "http://"..appname..".heroku.com/thumps/search?thump[deviceid]="..deviceId.."&thump[lat]="..latitudeText.."&thump[lng]="..longitudeText, method = "GET", sink = ltn12.sink.table(response) } local jsonget = Json.Decode(table.concat(response,'')) if(jsonget.success == true) then native.setActivityIndicator( false ); timer.cancel( event.source ) local alert = native.showAlert( "Thump!", jsonget.message, {"OK"} ) end attempts = attempts+1 timer.performWithDelay( 3000, retrieveThump ) end end timer.performWithDelay( 1000, retrieveThump ) else showError() end
Let's break it down:
if(jsonpost.success == true) then native.setActivityIndicator( true ); ... else showError() end
In the event that our "jsonpost" variable returns "success=true" from the server, we will set our ActivityIndicator on the device to true. This launches a native pacifier component that signals the user that the app is working on something. If we didn't receive a "success=true" from the server, we will call our generic error function and show the error alert box.
local attempts = 0 function retrieveThump( event ) ... end timer.performWithDelay( 1000, retrieveThump )
In this case we are setting an attempts variable outside the scope of our function to 0. We will use this later to put a cap on the number of requests our "retrieveThump" polling function can make. Corona has a built in timer class that allows us to call a function on an interval of time. The timer.performWithDelay function takes a number of milliseconds and a function as parameters.
if 10 == attempts then native.setActivityIndicator( false ); timer.cancel( event.source ) showError()
First, we will check whether we have executed this function 10 times. If this is the case, we are going to stop our ActivityIndicator by setting it to false, cancel our timer function, and then call our error function to tell the user something went wrong.
else local response = {} local r, c, h = http.request { url = "http://"..appname..".heroku.com/thumps/search?thump[deviceid]="..deviceId.."&thump[lat]="..latitudeText.."&thump[lng]="..longitudeText, method = "GET", sink = ltn12.sink.table(response) } local jsonget = Json.Decode(table.concat(response,'')) if(jsonget.success == true) then native.setActivityIndicator( false ); timer.cancel( event.source ) local alert = native.showAlert( "Thump!", jsonget.message, {"OK"} ) end attempts = attempts+1 timer.performWithDelay( 3000, retrieveThump ) end
If we haven't reached 10 attempts yet, we are going to execute an HTTP request to our server to search for our matched "thump." The function looks similar to the POST call we made earlier only we are passing the method GET in this case because we are trying to read and not write to the server.
If you remember from Part II, we created a search action in our Rails server that searches our database for a matching thump based on our GPS coordinates and similar timestamp. We pass this info to the server via the query string in the URL. Similar to the POST call, we are parsing the return from server as a JSON object and storing it in a local lua table variable called "jsonget." If we receive a success=true back from the server, we are going to stop our ActivityIndicator, stop our execution of the timer and display our message in an alert box. If this process fails, we will simply poll the server again in the same manner for a maximum of 10 attempts.
And there you have it! This tutorial should give you a good base to create different kinds of apps that share data via gestures. Some interesting additions might be to share via a simultaneous screen tap, or to extend the application to swap images either taken from the camera or the device's local photo library. Happy Thumping!
Comments