Гібридні Android-додатки для малюків



Сьогодні стаття про гібридних Android-додатки для малюків. У всіх сенсах цих слів. Ми поговоримо про написання найпростішого гібридного (Java+HTML+Javascript) Android програми для опитувань учнів початкових класів про їх рюкзаках. Передбачається мінімальне знання основ Java, HTML і JavaScript. Якщо Ви Android-розробник, хоч з мінімальним досвідом – Вам ця стаття навряд чи буде цікава, можна не відкривати. Всіх інших, хто ще тільки починає або думає почати розробку під Android, і кому цікаві основи розробки під Android, прошу під кат.

Вступна. Доньці (2 клас) було доручено зробити дослідницьку роботу на тему «Вплив ваги рюкзака на здоров'я дитини». Природно, в силу віку, основна робота припала на батьків. Вирішили провести опитування у класі на предмет того, у кого скільки важить рюкзак, хто сам скільки важить (для обчислення норми ваги рюкзака, який не повинен перевищувати 10% від маси дитини), хто носить рюкзак до школи і так далі. Для того, щоб урізноманітнити шкільні будні, вирішив зробити додаток під телефон на Android, який є у доньки, додаток для опитування. Спочатку планувалося включити в опитувальник вагу рюкзака і дитинча, але не встиг, і за підсумком ці параметри записали на листочок, по-старому. Залишилися тільки ті питання, на які діти могли відповісти самостійно.

Суть завдання: розробити програму для опитування учнів молодших класів для створення презентації доньці про те, наскільки шкідливо носити важкі рюкзаки. На картинці вище можна побачити, що у нас по підсумку вийде.
Відразу обмовлюся, зазвичай я розробляю рідні додатки для Android, HTML чисто для Web-додатків, але в цей раз було вирішено розробити гібридне додаток так як по-перше, швидше для даної задачі, а терміни були дуже стислі, по-друге, це було зручніше з точки зору функціоналу програми, в третіх, це був перший проект розробляється в Android Studio, хотілося мінімізувати можливі проблеми при використанні нового інструменту, щоб закінчити вчасно.

Отже, приступимо. Для початку, зрозуміло, Java-код (пояснення в коментарях), природно не забуваємо додати WebView в нашу Activity, присвоюємо йому id webView:



package com.probosoft.survey;

import android.os.Build;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Window;
import android.webkit.WebSettings;
import android.webkit.WebView;

public class MainActivity extends AppCompatActivity {

// Перевантажуємо метод onCreate, в ньому створюємо необхідний нам WebView і встановлюємо йому можливість масштабування
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R. layout.activity_main); // Показуємо нашим додатком, хто тут головний
WebView wv = (WebView) findViewById(R. id.webView); // Створюємо WebView – міні-браузер в додатку
WebSettings settings = wv.getSettings(); // Отримуємо клас налаштувань нашого міні-браузера
settings.setDisplayZoomControls(true); // Дозволяємо показати параметри масштабування для міні-браузера
wv.loadUrl("file://android_asset/html/index.html"); // Завантажуємо сторінку нашого додатка в міні-браузер
}
}

Тестовий поміщаємо index.html в папку assets/html. Пробуємо запустити. Нічого не виходить. З'ясовуємо важливий момент, що при зверненні до внутрішніх ресурсів слешів після протоколу має бути не два, а три. Міняємо:

wv.loadUrl("file://android_asset/html/index.html");

на:

wv.loadUrl("file:///android_asset/html/index.html");

Ура! Всі завантажилося. Починаємо писати HTML JS-код.

<!DOCTYPE html>
<html>
<head>
<title>Survey</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Bootstrap -->
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="vendor/bootstrap/css/bootstrap-theme.css" rel="stylesheet" media="screen">
<link href="vendor/jquery/jquery-ui.min.css" rel="stylesheet" media="screen">
<link href="vendor/jquery/jquery-ui.theme.css" rel="stylesheet" media="screen">
<link href="css/main.css" rel="stylesheet" media="screen">
<style>
#menu {
width: 100%;
}
</style>
</head>
<body>
<!—Тут підключаємо jQuery і Bootstrap -->
<script src="vendor/jquery/external/jquery/jquery.js"></script>
<script src="vendor/jquery/jquery-ui.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.min.js"></script>

<!—Тут підключаємо класи програми -->
<script src="js/consts.js"></script>
<script src="js/respondents.js"></script>
<script src="js/survey.js"></script>
<script src="js/questions.js"></script>
<script src="js/admin.js"></script>

<script src="data/respondents.js"></script>
<script src="data/questions.js"></script>

<div id="menuDiv">
<div class="page-header" onclick="javascript: showResults ();">
<center><h3 id="title"></h3></center>
</div>
<div style="display: none;" id="clearButton">
<input type="button" value="Clear all" onclick="javascript: clearAll ();"/><br/><br/>
</div>
<!-- Щоб не бентежити школярів і викладача, кнопка для демонстрації результатів приховано за замовчуванням -->
<div style="display: none;" id="showResults">
<input type="button" value="Show results" onclick="javascript: showResults (true);"/><br/><br/>
</div>
<!-- Область для відображення списку учнів http://www.w3schools.com/bootstrap/bootstrap_list_groups.asp -->
<div id="mainPane">
<ul class="list-group" id="menu">
</ul>
</div>
<!—Область для відображення результатів -->
<div id="resultsPane" style="display: none;">
<form method="post" action="http://serj.by/survey/api/storeSurveyData.php" id="storeForm">
<textarea name="surveyData" id="surveyData" style="width: 100%; height: 100%;" rows=25>
</textarea>
<input type="submit" value="Store on server"/>
<input type="hidden" name="redirectURL" value="."/>
</form>
</div>
</div>
<div id="thanks">Дякую за відповіді!<br/><br/><input type="button" title="Ok" value="будь Ласка!" id="ok"/></div>
<script>

var respondents;

var adminMode = true; // 

function clearAll () {
try {
if(typeof(Storage) !== "undefined") {
this.storage = localStorage;
}
} catch (e) {
alert ("Local storage error: "+e);
}
this.storage.clear ();
}

function init () {
$("#mainPane").show ();
$("#resultsPane").hide ();
if (adminMode) $("#showResults").show ();

$("#title").html"Виберіть учня");
var res = dataRespondents;

res.forEach (function (element, i, arr) {
element.id = i+1;
});

respondents = new Respondents (res);
respondents.renderRespondents ($("#menu"));

$("#storeForm redirectURL").val (document.location.href);
}

init ();
</script>
</body>
</html>

Спочатку я пробував використовувати AJAX для завантаження даних, але досить швидко переконався, що всередині WebView, і на локальних ресурсах він просто не працює. Тому, для завантаження контенту довелося використовувати досить суперечливий метод – зберегти всі дані про респондентів у глобальний масив.
Пробуємо запускати. Знову не працює. У чому ж справа? Для нашого WebView ми не дозволили виконання JavaScript. Виправимо. Додамо до Java коду:
settings.setJavaScriptEnabled(true);

Тепер працює. Ура! Нам потрібно було дозволити виконання JavaScript в нашому WebView. Пишемо класи функціоналу. Код приводити не буду. З ним можна познайомитися на GitHub проекту (в кінці статті). Тут описую основні проблеми, з якими може зіткнутися початківець розробник гібридних додатків.

Далі ми підключаємо LocalStorage для збереження даних наших анкет. Для цього ми використовуємо «клас» Survey в survey.js. Традиційно, коментарі до коду в ньому самому.

/**
* Represents survey for particular respondent
* @param integer id Id of respondent
*/
var Survey = function (in_respondentId, in_respondent)
{
var respondentId; // Id опитуваного учня
var respondent = null; // Об'єкт, що представляє опитуваного учня
var questions = null; // Масив питань

var parent = this; // Брудний хак, який дозволяє отримати контекст об'єкта всередині вкладених контекстів

var storage = null; // Змінна содержжащая LocalStorage на випадок якщо його доведеться повторно використовувати

this.answers = []; // Масив відповідей на запитання опитувальника

/**
* Begins survey for chosen respondent
*/
this.start = function ()
{ 
var res = dataQuestions; // Ініціалізуємо змінну з питаннями

parent.questions = new Questions (res, parent.respondent); // Створюємо перший питання
parent.questions.start (); // Розпочинаємо опитування
}

/**
* Stores all answers in storage
*/
this.collectAnswersAndStore = function ()
{
this.storage.setItem (window.UNIQUE_STORAGE_ID+this.respondentId, JSON.stringify (this.answers)); // Зберігаємо результат опитування
window.init (); // Повертаємося на головний екран
}

this.surveyOption = function (val)
{
this.answers.push (val); // Запам'ятовуємо відповідь учня
//alert (this.answers);
if (!this.questions.advanceQuestion ()) // Перевіряємо, чи є ще запитання
{
this.collectAnswersAndStore (); // Якщо немає більше запитань, зберігаємо відповіді
}
}

// Ініціалізація змінних
this.respondentId = in_respondentId;
this.respondent = in_respondent;

// Намагаємося отримати доступ до сховища
try {
if(typeof(Storage) !== "undefined") {
this.storage = localStorage; 
}
} catch (e) {
alert ("Local storage error: "+e);
}
}

Все ніби працює, але нічого не зберігається. Попутно дізнаємося цікаву подробицю – в останніх версіях Android alert в WebView робить… нічого. Зовсім нічого. Ні помилки, ні якогось повідомлення у консолі. Просто як-ніби його й немає. З'ясовуємо, що для використання LocalStorage в WebView нам потрібна установка додаткових прапорів для WebView. Зробимо це:


settings.setDomStorageEnabled(true);
settings.setDatabaseEnabled(true);

Ура! LocalStorage заробив. Довго чи коротко, за вихідні щось придатне для використання було написано. Дитина був відправлений у гімназію з телефоном, на якому було встановлено дане поділився. Хлопці (вже з допомогою вчительки, або самостійно – це залишилося за кадром) сумлінно пройшли анкетування, і не було даних лише по чотирьом учням, які з тих чи інших причин були відсутні на заняттях.

Тепер переді мною постала проблема: потрібно якось витягти дані (так-так, про це треба було думати спочатку, але не забуваємо, що програма розроблялася в страшному цейтноті, і передбачалося, що ця задача не з складних і не з невідкладних і її цілком можна вирішити потім).

Основна проблема виявилася в тому, що у доньки досить старий і слабкий телефон (вибирався з урахуванням фактора «щоб не шкода було трохи»). Пробував витягнути дані через Bluetooth, відправкою AJAX-запиту на сервер, створювалася форма для відправки і т. д. – без варіантів. Текст DIV е не вибирається, за підсумком DIV був перероблений в TextArea (що можна спостерігати у фінальному коді на GitHub). Звідти вдалося виділити і скопіювати текст з результатами опитування та переслати його на мій E-mail. У підсумку це виявився єдиний робочий варіант.

Пишемо скрипт, щоб дані виявилися в Excel таблиці. На даному етапі виявилася ще одна проблема початкової архітектури – ті учні, які не проходили анкетування позначалися простий рядком «Анкета заповнена». Природно, regexp'и, які були розраховані на нормальні дані на цих рядках «спотикалися». Благо таких рядків було всього чотири. Вручну вони були видалені з підсумкових результатів і ми отримали цілком адекватну вибірку (реальні імена замінені на плейсхолдеры):

Учень 1, ім'я: Коли,Ніколи,Мені все подобається в моєму рюкзаку 2. Учень 2, ім'я: Коли як,Іноді трапляються,Занадто важкий 4. Учень 3, ім'я: Дорослі,Іноді трапляються Просто незручно, але пояснити не можу 5. Учень 5, ім'я: Я,Ніколи,Мені все подобається в моєму рюкзаку 6. Учень 6, ім'я: Я,Іноді трапляються,Мені все подобається в моєму рюкзаку 7. Учень 7, ім'я: Я,Іноді трапляються,Мені все подобається в моєму рюкзаку 8. Учень 8, ім'я: Я,Ніколи,Мені все подобається в моєму рюкзаку 9. Учень 9, ім'я: Я,Іноді трапляються,Мені все подобається в моєму рюкзаку 10. Учень 10, ім'я: Я,Іноді трапляються,Мені все подобається в моєму рюкзаку 11. Учень 11, ім'я: Я,Іноді трапляються,Мені все подобається в моєму рюкзаку 12. Учень 12, ім'я: Я,Іноді трапляються,Мені все подобається в моєму рюкзаку 14. Учень 14, ім'я: Коли,Ніколи,Мені все подобається в моєму рюкзаку 16. Учень 16, ім'я: Я,Завжди що-небудь є,Мені все подобається в моєму рюкзаку 17. Учень 17, ім'я: Я,Іноді трапляються,Мені все подобається в моєму рюкзаку 18. Учень 18, ім'я: Я,Завжди що-небудь є,Мені все подобається в моєму рюкзаку 19. Учень 19, ім'я: Я,Іноді трапляються Просто незручно, але пояснити не можу 21. Учень 21, ім'я: Коли як,Завжди що-небудь є,Мені все подобається в моєму рюкзаку 22. Учень 22, ім'я: Я,Ніколи,Надто важкий 23. Учень 23, ім'я: Я,Іноді трапляються,Мені все подобається в моєму рюкзаку 24. Учень 24, ім'я: Я,Іноді трапляються,Занадто важкий

Можна було, звичайно, перетворити все це на телефоні, але так здалося простіше. Це вже легко розбирається простим регулярним виразом. Пишемо PHP-скрипт:

<pre>
<?php

function normLastOption ($s)
{
switch ($s)
{
case "Я":
return "Мені все подобається в моєму рюкзаку";
case "Занадто":
return "Занадто важкий";
case "Просто":
return "Просто незручно, але пояснити не можу";
}
}

$results = [];
$data = "Дані";
preg_match_all ("/(((\d+)\. (.+), (.+): (.+),(.+),(.+)))+ /U", $data, $results);
print_r ($results);
$csv = "";
foreach ($results [3] as $key = > $value)
{
$csv .= "Учень ".($key+1).",".$results [6] [$key].",".$results [7] [$key].",".normLastOption($results [8] [$key])."\n";
}
print $csv;
$f = fopen ("survey.csv", "w");
fwrite ($f, $csv);
fclose ($f);
?>

Як видно з коду, останній параметр за рахунок присутності прогалин спочатку парсился неправильно. Як тут написати правильне регулярне вираз, я не став заморочуватися знову ж у зв'язку з відсутністю часу. Якщо хто підкаже в коментарях – велике спасибі!

Таким чином, ми розробили просте гібридне додаток для Android і навіть витягли з нього дані.
Повний код програми на Github – Ліцензія MIT. Якщо кому потрібно таке поділився «на коліні» — використовуйте на здоров'я!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.