Добро пожаловать в часть III серии «Совместное использование данных с помощью жестов». Во второй части мы создали наш сервер-посредник в Ruby on Rails. Этот серверный процесс будет действовать как канал между двумя устройствами, которые пытаются общаться жестом, который мы называем «удар». Когда два устройства объединяются, сервер сопоставляет их, вычисляя их близость друг к другу через координаты GPS, а также почти идентичную метку времени, когда они открывали связь с сервером. Как только это совпадение будет выполнено, сервер будет обмениваться сообщениями, набранными в мобильном приложении, имитируя обмен данными между устройствами.
В третьей части мы развернем наше серверное приложение на платформе heroku, а затем обновим наше мобильное приложение для связи с ним.
Для начала мы изменим наш файл переноса таблицы thumps, чтобы он был совместим с Postgres. Вы можете найти этот файл в каталоге «db / migrate», и он будет называться примерно так: TIMESTAMP_create_thumps.rb. Самый простой способ развернуть приложение heroku — это использовать их службу общих баз данных, которая, скорее всего, является Postgres вместо MySQL. Мы собираемся заменить следующие строки:
1
2
|
t.decimal :lat, :precision=>8, :scale=>8
t.decimal :lng, :precision=>8, :scale=>8
|
с этими новыми линиями:
1
2
|
t.decimal :lat, :scale=>8
t.decimal :lng, :scale=>8
|
Postgres обрабатывает большие десятичные поля иначе, чем MySQL, так что это необходимое изменение, чтобы гарантировать, что мы получим необходимую точность данных в наших полях широты и долготы.
Поскольку это приложение Rails 2.3.5, мы будем использовать более старый метод heroku для установки гемов, создав файл .gems в корне нашего проекта Rails. Большинство из вас, вероятно, привыкли использовать Bundler для такого рода задач, однако, поскольку плагин geokit не был обновлен до совместимости с Rails 3.0, нам нужно действовать со старыми соглашениями Rails 2.
Мы просто добавляем следующее в наш файл .gems:
1
2
3
|
rails -v 2.3.5
pg
geokit —version ‘=1.5.0’
|
Здесь мы указываем драгоценный камень rails и версию, которую мы используем для этого проекта, а также драгоценный камень postgres и версию 1.5.0 драгоценного камня geokit.
Теперь мы можем начать наше развертывание! Давайте начнем с создания локального git-репозитория внутри нашего проекта. Все, что нам нужно сделать, это запустить следующую команду в корневом каталоге нашего проекта Rails:
1
|
$ git init
|
Прежде чем мы перейдем к этому репозиторию, нам нужно создать наше приложение на платформе heroku. Если вы еще не зарегистрировали бесплатную учетную запись heroku, просто зарегистрируйтесь по адресу https://api.heroku.com/signup. Если в вашей системе еще не установлен гем heroku, вы можете сделать это, выполнив следующую команду:
1
|
$ sudo gem install heroku
|
После установки gem выполните следующую команду из корневого каталога проекта:
1
|
$ heroku create —stack bamboo-ree-1.8.7
|
Здесь мы указываем стек bamboom-ree, поскольку это приложение Rails 2. В случае, если вы только что создали новую учетную запись, она может запросить учетные данные вашей учетной записи heroku. После ввода эти учетные данные будут сохранены для будущих взаимодействий с серверами heroku. Если все пойдет хорошо, геройку должен ответить примерно так:
1
2
|
Created http://APPNAME.heroku.com/ |
Git remote heroku added
|
Здесь я заменил фактическое имя приложения и субдомен на заполнитель APPNAME. Запишите это APPNAME, так как мы будем использовать его позже. Теперь мы собираемся передать файлы нашего проекта в локальный репозиторий git, который мы создали ранее. Это так же просто, как запустить эти две команды:
1
2
|
$ git add .
$ git commit -m «my first commit»
|
Как только проект будет полностью передан в локальное git-репо, нам нужно отправить его в удаленный репозиторий, который был создан при запуске команды heroku create.
1
|
$ git push heroku master
|
Gem Heroku позволяет выполнять команды удаленного рейка на сервере с помощью команды «Heroku Rake». Чтобы завершить развертывание, мы должны запустить миграцию нашей базы данных, которая сгенерирует нашу таблицу thumps в базе данных heroku.
1
|
$ heroku rake db:migrate
|
Congrats! Вы успешно развернули наше приложение Thump Server на платформе heroku. Теперь вернемся к мобильному приложению!
Давайте откроем наш файл main.lua в нашем приложении Corona и добавим следующие строки вверху:
1
2
3
4
|
http = require(«socket.http»)
ltn12 = require(«ltn12»)
url = require(«socket.url»)
require(«Json»)
|
Нам нужны некоторые библиотеки, которые позволят нам создать сокет-соединение с нашим серверным приложением. Мы также включим библиотеку анализа JSON, чтобы понять объекты ответа, которые сервер отправит обратно.
Помните APPNAME, который был дан, когда мы впервые создали приложение heroku? Пришло время использовать это сейчас:
1
|
local appname = «APPNAMEHERE»
|
Эта переменная будет позже объединена с другими, чтобы сгенерировать URL нашего сервера для внешней связи.
В первой части у нас было приложение, отображающее окно с сообщением, когда оно обнаруживало «удар» или «встряхивание». Поскольку нам нужно передать это сообщение на сервер, мы удалим следующую строку внутри нашей функции getThump:
1
|
local alert = native.showAlert( «Thump!», «Location: «..latitudeText..»,»..longitudeText..»\r\nMessage: «..textField.text, {«OK»} )
|
Теперь мы собираемся добавить некоторые функциональные возможности в этот метод getThump для отправки нашего сообщения на сервер. Давайте разберем это:
1
2
3
4
|
local message = textField.text
local post = «thump[deviceid]=»..deviceId..»&thump[lat]=»..latitudeText..»&thump[lng]=»..longitudeText..»&thump[message]=»..message
local response = {}
|
Здесь мы генерируем наши переменные для отправки на сервер. Переменная «post» настроена в формате строки запроса, и наш «response» пока объявлен как пустой объект таблицы.
01
02
03
04
05
06
07
08
09
10
11
|
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,»))
|
Здесь мы выполняем HTTP-запрос типа POST на наш сервер. Мы добавляем переменную appname в качестве субдомена для URL. Заголовки являются стандартными для типичного почтового звонка. В поле «content-length» синтаксис lua, заключающийся в том, что символ # ставится перед переменной, выводит длину в символах этой строки. Поскольку мы хотим сохранить отклик нашего сервера в переменной с именем «response», наша последняя строка будет декодировать эту переменную как объект JSON и создаст объект таблицы lua, чтобы мы могли обращаться к полям внутри него.
В случае ошибки связи, мы должны предупредить пользователя, что что-то пошло не так. Мы создадим общий метод showError () для отображения окна предупреждения пользователю, если это произойдет:
1
2
3
|
local function showError()
local alert = native.showAlert( «Error!», «Please try your thump again!», {«OK»} )
end
|
Из-за того, что Rails является однопоточным по своей природе и так как свободные аккаунты heroku позволяют запускать только один серверный процесс; как только мобильное приложение завершит вызов POST для отправки данных, мы собираемся опросить наш сервер, запросив объект ответа. Хотя это, возможно, не самый идеальный способ для разработки этого, это позволяет нам запускать приложения такого типа с минимальными ресурсами сервера heroku.
Вот наша логика опроса ниже:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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
|
Давайте разберемся с этим:
1
2
3
4
5
6
|
if(jsonpost.success == true) then
native.setActivityIndicator( true );
…
else
showError()
end
|
В случае, если наша переменная «jsonpost» возвращает с сервера «success = true», мы установим для ActivityIndicator на устройстве значение true. Это запускает собственный компонент соски, который сигнализирует пользователю, что приложение работает над чем-то. Если мы не получили «success = true» с сервера, мы вызовем нашу общую функцию ошибки и покажем окно предупреждения об ошибке.
1
2
3
4
5
6
|
local attempts = 0
function retrieveThump( event )
…
end
timer.performWithDelay( 1000, retrieveThump )
|
В этом случае мы устанавливаем переменную попытки вне области действия нашей функции на 0. Мы будем использовать это позже, чтобы ограничить число запросов, которые может выполнить наша функция опроса «retrieveThump». В Corona есть встроенный класс таймера, который позволяет нам вызывать функцию с интервалом времени. Функция timer.performWithDelay принимает количество миллисекунд и функцию в качестве параметров.
1
2
3
4
|
if 10 == attempts then
native.setActivityIndicator( false );
timer.cancel( event.source )
showError()
|
Сначала мы проверим, выполнили ли мы эту функцию 10 раз. Если это так, мы собираемся остановить наш ActivityIndicator, установив для него значение false, отменить нашу функцию таймера, а затем вызвать нашу функцию ошибок, чтобы сообщить пользователю, что что-то пошло не так.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
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
|
Если мы еще не достигли 10 попыток, мы собираемся выполнить HTTP-запрос к нашему серверу, чтобы найти наш соответствующий «удар». Функция выглядит аналогично вызову POST, который мы сделали ранее, только в этом случае мы передаем метод GET, потому что мы пытаемся читать, а не записывать на сервер.
Если вы помните из части II, мы создали поисковое действие на нашем сервере Rails, которое ищет в нашей базе данных совпадение, основанное на наших GPS-координатах и аналогичной отметке времени. Мы передаем эту информацию на сервер через строку запроса в URL. Подобно вызову POST, мы анализируем возврат с сервера как объект JSON и сохраняем его в локальной переменной таблицы lua с именем «jsonget». Если мы получим сообщение об успехе = true с сервера, мы собираемся остановить наш ActivityIndicator, остановить выполнение таймера и отобразить наше сообщение в окне предупреждения. Если этот процесс завершится неудачно, мы просто повторим опрос сервера тем же способом максимум за 10 попыток.
И вот оно! Этот учебник должен послужить хорошей основой для создания различных видов приложений, которые обмениваются данными с помощью жестов. К некоторым интересным дополнениям можно отнести одновременное касание экрана или расширить приложение для обмена изображениями, снятыми с камеры или из локальной библиотеки фотографий устройства. Счастливая грудь!