Использование SQLite в Ionic приложении.

Введение

SQLite — это компактная встраиваемая реляционная база данных, которая используется во многих операционных системах, но особенную популярность получила на мобильных в связи с малым весом.

Ionic — это популярная платформа, основанная на Apache Cordova. Данный фреймворк позволяет создавать мобильные кроссплатформенные приложения с помощью AngularJS от Google и использовать нативные функции мобильных операционных систем.

Благодаря Ionic возможно разработать приложения для самых распространенных мобильных операционных систем — Google Android и Apple iOS. Для того, чтобы использовать полноценные возможности мобильных устройств на перечисленных системах, предусмотрены специальные плагины, расширяющие функционал Ionic приложения и позволяющие работать с нативными фишками устройств.

Для Android и iOS приложения очень важно сохранять информацию вводимую пользователем, так как мобильное устройство не всегда имеет доступ в интернет. Человек хочет пользоваться приложением в любом месте вне зависимости от наличия глобальной сети. Он может отправиться в длительную поездку и созданное приложение должно полноценно функционировать.

Так как Ionic приложение это по сути обычная веб-страница на Angular JS, то она не может сохранять данные надолго: все, что не собралось в cache или local storage, стирается после закрытия приложения. А хранить данные в cache и локальной БД браузера просто ненадежно, так как они периодически очищаются. Можно сделать вывод, что JavaScript не предназначен для выборки, обработки и записывания данных, необходимо специальное решение для хранения информации в базе данных мобильного устройства.

Такое решение — мобильная база данных SQLite для фреймворка Cordova. Создатели данной БД уже снискали славу у мобильных Android и iOS разработчиков. Работать с SQLite довольно просто, данные занимают мало места, доступ к ним можно получить стандартными средствами SQL-языка. Появляются широкие возможности манипулирования данными.

Установка и подготовка

Для начала убедитесь что у вас установлен пакетный менеджер npm (входит в комплект Node JS) и фреймворки Cordova и Ionic.

Если нет, то:

  1. Скачайте npm и Node JS с официального сайта https://nodejs.org/en/ или если у вас Linux используйте стандартный установщик через консоль, например, в Ubuntu: sudo apt-get install nodejs & sudo apt-get install npm
  2. Далее в консоли: npm install -g cordova & npm install -g ionic

Теперь давайте создадим простое приложение, чтобы испытать работу SQLite.

Создание приложения

Для того, чтобы в Ionic создать простейшее приложение достаточно воспользоваться программой Ionic Lab (http://lab.ionic.io/) или написать в консоли команду ionic start НазваниеВашегоПриложения(естественно на английском) tabs. Последний параметр это шаблон приложения. Кроме tabs (вкладки) также доступно blank (чистый шаблон) и sidemenu (с боковым меню). Более подробно можно узнать в документации доступной по ссылке: http://ionicframework.com/docs.

Для нашего примера шаблон не важен.

Настройка базы данных SQLite

Теперь установим плагин Cordova SQLite для созданного приложения. Для этого в корне, то есть в папке с названием вашего приложения пишем в консоли:

cordova plugin add https://github.com/brodysoft/Cordova-SQLitePlugin.git

Ждем пока плагин установится и качаем дополнительные файлы Cordova по ссылке https://github.com/driftyco/ng-cordova/archive/master.zip. В архиве нам понадобится файл ng-cordova.min.js, его надо положить в директорию ВашеПриложение/www/js и дать на него ссылку в index.html ВЫШЕ cordova.js, это важно!

Далее в главный модуль приложения, который лежит в файле app.js, добавляем подключаемый модуль “ngCordova”.

Инициализация базы данных

Чтобы сконфигурировать базу данных для нашего приложения, необходимо вызвать плагин в блоке run Angular кода. Но перед этим нужно объявить переменную базы данных вне кода нашего модуля Angular JS, например в самом начале файла app.js до объявления модуля:

var db = null;

Блок run идет сразу после главного модуля приложения в том же файле app.js, там уже по умолчанию есть некоторые настройки ionic, которые относятся к виртуальной клавиатуре и статус бара мобильных устройств. Нам нужно в той же функции $ionicPlatform.ready, которая отвечает за полную загрузку приложения и, что самое главное, готовность работать с нативными функциями устройства, инициализировать нашу базу данных:

if (window.cordova) {
    // Инициализация БД на устройстве
    db = $cordovaSQLite.openDB({name: "webapp5.db", iosDatabaseLocation: 'default'});
}
else {

    // Инициализация БД в браузере
    db = window.openDatabase("webapp5.db", '1', 'webapp5', 1024 * 1024 * 100);
 }

Если оболочка, как указано в условном операторе, cordova, то используется мобильный плагин SQLite, иначе используются возможности Web SQL и Local Storage. Последний вариант полезен при отладки приложения в браузере перед компилированием для устройства, так как плагин SQLite в веб-браузере выдает ошибку.

Далее согласно синтаксису обращения к базе данных мы создали несколько таблиц с определенными полями:

   console.log('Создаются все таблицы...');
   $cordovaSQLite.execute(db, "create table if not exists accounts (id integer primary key AUTOINCREMENT, user_id integer, title text, balance integer, sync tinyint, created datetime)");
    $cordovaSQLite.execute(db, "create table if not exists goals (id integer primary key, title text, price integer, user_id integer, balance integer, sync tinyint, created datetime)");

Обращения к базе достаточно простое, оно содержит несколько параметров функции execute: название базы данных (в нашем случае раннее сохраненная переменная db), SQL-запрос к базе данных и необязательный параметр — передаваемые данные в виде массива (его рассмотрим позже).

Callback функции

Скорее всего вам нужно будет выводить информацию, взятую в базе данных. Для всех операций с извлеченными данными в синтаксисе SQLite есть callback функции, которые асинхронно выполняются после выполнения SQL-запроса. Callback функции, пишутся так:

   $cordovaSQLite.execute(db, "create table if not exists config (id integer primary key, language varchar(255), db_version integer, user_id integer, sync tinyint, updated datetime)").then(function(response){
   console.log(‘Таблица конфигурации создана, ответ операции ’ + response)
   }, function(error) {
   console.log(error)
   });

Их можно использовать для работы с полученными из БД данными, а также для отладки последовательности проведенных операций. Первая функция будет выполнятся в случае удачи операции, вторая в случае ошибки. Очень важно помнить что асинхронные функции then выполняются после всего кода. Поэтому в таком случае:

   var data;
   $cordovaSQLite.execute(db, “select * from goals”).then(function(response){
data = response.rows.item(0).title
   });
   cosnole.log(data);

вывод переменной ниже execute вернет в консоли undefined, так как callback функция еще не запущена и переменная пока не имеет значения, присвоенного в функции.

Зато если вывести переменную внутри callback функции, то все получится:

   var data;
   $cordovaSQLite.execute(db, “select * from goals”).then(function(response){
data = response.rows.item(0).title
   console.log(data);
   });

Следовательно, любые манипуляции с извлеченными из БД данными необходимо проводить в callback функции обращения.

Сама функция $cordovaSQLite.execute возвращает promise (обещание):

   var promise = $cordovaSQLite.execute(db, “select title from goals where id=1”).then(function(response){
   return response;
   });

Более подробно про promise можно почитать здесь: https://learn.javascript.ru/promise.

Настройка Модели приложения

Чтобы поддерживать MVC модель нашего приложения все операции с данными будем проводить в фабрике (factory) Angular JS:

   .factory('Data', ['$cordovaSQLite', '$filter', function ($cordovaSQLite, $filter) {
      function insertData(query, data) {
         var date = new Date();
         var created = $filter('date')(date, 'yyyy-MM-dd HH:mm:ss');
         data.push(created);
         var insertId = $cordovaSQLite.execute(db, query, data).then(function (result) {
         return result.insertId;
        }, function (error) {
                console.log(error);
           });
            return insertId;
        }
        function getData(query) {
            var data = [];
            $cordovaSQLite.execute(db, query, []).then(function (result) {
                // Проверяем длину объекта, который возвращает результат
                if (result.rows.length > 0) {
                    for (var i = 0; i < result.rows.length; i++) {
                        data[i] = result.rows.item(i);
                    }
                }
                else {
                    console.log("No results found");
                }
            }, function (error) {
                console.log(error);
            });
            return data;
        }
        return {
            getData: function (query) {
                return getData(query);
            },
             insertData: function (query, data) {
                return insertData(query, data);
            }
        }
    }]);

Фабрика под названием Data имеет две функции — первая insertData вносит данные, вторая getData извлекает. Разница в том, какие типы переменных возвращают данные функции. Первая функция возвращает обычный массив data, а вторая функция возвращает promise, поэтому использовать insirtId можно только после resolve promise (то есть разрешения обещания с помощью then). Обе реализации могут пригодится в будущем приложении.

Сохранение информации в базу данных

Создадим простую форму, которая будет передавать данные из View в наш контроллер, а далее с помощью фабрики в базу данных.

<form ng-submit="SaveGoal(AddGoalData.title, AddGoalData.amount)">
                <div class="list list-inset">
                    <label class="item item-input">
                        <i class="icon ion-star placeholder-icon"></i>
                        <input required type="text"
                               placeholder="title"
                               class="item item-input" ng-model="AddGoalData.title">
                    </label>
                </div>
                <div class="list list-inset">
                    <label class="item item-input">
                        <i class="icon ion-social-usd placeholder-icon"></i>
                        <input required type="text" pattern="[0-9]*"
                               placeholder="how much does your goal cost?"
                               class="item item-input" ng-model="AddGoalData.amount">
                    </label>
                </div>
                <button type="submit" class="button button-balanced button-block"><i
                        class="icon ion-checkmark"></i>Add
                </button>
            </form>

Создадим в контроллере функцию SaveGoal и там вызовем функцию фабрики:

   myApp.controller('MainCtrl', ['$scope', 'Data', function ($scope, Data) {
   $scope.SaveGoal = function (title, price) {
   Data.insertData("insert or replace into goals (id, title, price, balance, sync, user_id, created) values (1,?,?,?,?,?,?)", [title, price, 0, 0, 1]);
   }
}]);

В функцию insertData, которая принадлежит фабрике Data, мы передали следующие параметры — запрос к базе данных и массив передаваемых данных, в нашем случае это как обычные числа (также может быть просто текст), так и переменные.

Получение и вывод информации из базы данных

Далее выведем информацию, которая уже была внесена в базу данных.

Для наглядности просто будем делать это выше нашей формы. Вы же можете делать это где угодно, на любой странице. Для начала напишем функцию которая будет вынимать данные из базы (все поля):

   function getGoals () {
   $scope.data.goals = [];
   $scope.data.goals = Data.getData(“select * from goals”);
   }

В результате выполнения данной функции должен получиться ассоциативный массив, где ключ — это название поля, а значение — значение поля соответственно. Вызовем эту функцию в контроллере страницы, не забыв перед этим объявить массив данных:

   myApp.controller('MainCtrl', ['$scope', 'Data', function ($scope, Data) {
         $scope.data = [];
         getGoals ();
      $scope.SaveGoal = function (title, price) {
      Data.insertData("insert or replace into goals (id, title, price, balance, sync, user_id, created) values (1,?,?,?,?,?,?)", [title, price, 0, 0, 1]);
      }
   }]);

Далее оформим html для правильного отображения собранных данных. Для этого будем использовать полезные возможности Angular JS. Итак, выше формы напишем:

<div class="card">
            <div class="item item-divider">
                <h2>Цели</h2>
            </div>
            <div ng-hide="(data.goals.length > 0) ? true : false;" class="item item-text-wrap">
                <p>У вас еще нет целей</p>
            </div>
            <div class="item item-text-wrap" ng-repeat="goal in data.goals>
                <span>Название: {{goal.title}}</span>
                <p>Цена: {{goal.price}}</p>
     <p>Баланс цели: {{goal.balance}}</p>
     <p>Дата создания: {{goal.created}}</p>
            </div>
</div>

Для того, чтобы обновление наших данных на странице происходило после каждого добавления через форму новых данных, будем вызывать функцию getGoals в функции $scope.SaveGoal:

   myApp.controller('MainCtrl', ['$scope', 'Data', function ($scope, Data) {
         $scope.data = [];
         getGoals ();
   $scope.SaveGoal = function (title, price) {
   Data.insertData("insert or replace into goals (id, title, price, balance, sync, user_id, created) values (1,?,?,?,?,?,?)", [title, price, 0, 0, 1]);
getGoals ();
   }
   }]);

Заключение

На этом все, осталось только убедиться, что все работает. Если хотите попробовать на устройстве, то необходимо выполнить команды ionic по компиляции приложения. Команда ionic build далее операционная система Android или iOS. Таким образом, мы с помощью плагина Cordova SQLite реализовали надежное локальное хранение данных в приложении. Спасибо за внимание, конечно, это малый шаг по созданию приложения на платформе Ionic, но надеюсь этот урок будет для вам полезным.