Fábrica de Código

Nesse tutorial vamos criar um app utilizando os plugins SQLite, Toast, Local notification e Badge da biblioteca ngCordova.

O código fonte de exemplo está disponível no GitHub.

O que é ngCordova?

O ngCordova é um AngularJS wrapper desenvolvido para utilizar os plugins mais populares do Cordova com o Ionic Framework. Além das funcionalidades que serão apresentadas nesse tutorial, com essa biblioteca é possível tirar uma foto, acessar sua localização, entre outros.

O ngCordova é open source e utiliza a licença MIT, ou seja, você pode utiliza-lo inclusive comercialmente.

O app que será criado

Iniciando o projeto

Vamos começar criando o projeto com o Ionic.

ionic start ToDoApp blank

Para instalar o ngCordova utilizando o bower com a linha de comando abaixo ou você pode baixar os arquivos diretamente do site e incluir no projeto.

$ bower install ngCordova

É necessário incluir o arquivo ng-cordova.js ou ng-cordova.min.js no arquivo index.html antes do arquivo cordova.js.

<script src="lib/ngCordova/dist/ng-cordova.js"></script> 
<script src="cordova.js"></script>

Após esse processo é necessário injetar o ngCordova no seu modulo angular.

angular.module('myApp', ['ngCordova'])

Observações importantes:

  • Os pluguins do cordova só funcionam no emulador ou no próprio dispositivo, ou seja, não é possível testar diretamente no navegador.
  • Antes de utilizar qualquer método de um plugin é importante verificar se o cordova foi totalmente carregado no dispositivo. Para fazer isso basta seguir o exemplo abaixo.
$ionicPlatform.ready(function() {
   $cordovaPlugin.someFunction().then(success, error);
});

Instalando os plugins

Utilizando CLI, navegue até a pasta do seu projeto e execute os comandos abaixo para cada plugin.

SQLite

Esse é um plugin para acessar um banco de dados SQLite local.

$ cordova plugin add https://github.com/litehelpers/Cordova-sqlite-storage.git

Toast

Esse plugin exibe mensagens em um simples popup. Excelente para mensagens não intrusivas.

$ cordova plugin add https://github.com/EddyVerbruggen/Toast-PhoneGap-Plugin.git

Local notification

Esse plugin gera notificações para o usuário. Pode ser utilizado para avisar ao usuário que ele tem alguma tarefa a fazer no app.

$ cordova plugin add https://github.com/katzer/cordova-plugin-local-notifications.git

Badge

Esse plugin incrementa o numero que fica ao lado do ícone do aplicativo. Ideal para mostrar, por exemplo, a quantidade de tarefas que o usuário tem pendente no app.

$ cordova plugin add https://github.com/katzer/cordova-plugin-badge.git

Como utilizar o plugin SQLite

Toda a documentação do pluguin você pode encontrar clicando aqui ou aqui.

Os códigos abaixo são exemplos de como utilizar o plugin.

Inserindo:

module.controller('MyCtrl', function($scope, $cordovaSQLite) {
    var db = $cordovaSQLite.openDB({ name: "ToDo.db", iosDatabaseLocation: 'Default' });

    var query = 'insert into ToDo (title, description, done) values (?,?,?)';
    $cordovaSQLite.execute(db, query, ['Titulo', 'Descrição', 0]).then(function(result) {
      console.log("insertId: " + result.insertId);
      console.log("rowsAffected: " + result.rowsAffected);
    }, function (error) {
      console.error(error);
    });
});

Atualizando:

module.controller('MyCtrl', function($scope, $cordovaSQLite) {
    var db = $cordovaSQLite.openDB({ name: "ToDo.db", iosDatabaseLocation: 'Default' });

    var query = 'update ToDo set title = ?, description = ?, done = ? where id = ?';
    $cordovaSQLite.execute(db, query, ['Titulo', 'Descrição', 0, 1]).then(function(result) {
      console.log("insertId: " + result.insertId);
      console.log("rowsAffected: " + result.rowsAffected);
    }, function (error) {
      console.error(error);
    });
});

Buscando dados:

module.controller('MyCtrl', function($scope, $cordovaSQLite) {
    var db = $cordovaSQLite.openDB({ name: "ToDo.db", iosDatabaseLocation: 'Default' });

    var query = 'select * from ToDo';
    $cordovaSQLite.execute(db, query, []).then(function(result) {
        for (var i = 0; i < result.rows.length; i++) {
            var row = result.rows.item(i);
            console.log(row);
        }       
    }, function (error) {
      console.error(error);
    });
});

Deletando:

module.controller('MyCtrl', function($scope, $cordovaSQLite) {
    var db = $cordovaSQLite.openDB({ name: "ToDo.db", iosDatabaseLocation: 'Default' });

    var query = 'delete ToDo where id = ?';
    $cordovaSQLite.execute(db, query, [1]).then(function(result) {
      console.log("insertId: " + result.insertId);
      console.log("rowsAffected: " + result.rowsAffected);
    }, function (error) {
      console.error(error);
    });
});

Como utilizar o plugin Toast

Toda a documentação do pluguin você pode encontrar clicando aqui ou aqui.

O código abaixo é um exemplo de como utilizar o plugin.

module.controller('MyCtrl', function($cordovaToast) {
  $cordovaToast
    .show('Sua mensagem aqui.', 'long', 'center')
    .then(function(success) {
      // success
    }, function (error) {
      // error
    });
});

Como utilizar o plugin Local notification

Toda a documentação do pluguin você pode encontrar clicando aqui ou aqui.

O código abaixo é um exemplo de como utilizar o plugin.

module.controller('MyCtrl', function($cordovaLocalNotification) {
    $cordovaLocalNotification.schedule({
        id: 1, //id da notificação
        title: 'Tarefa salva com sucesso.',
        text: 'Você tem uma tarefa para ser executada. Mãos a obra! =]'
   });
});

Como utilizar o plugin Badge

Toda a documentação do pluguin você pode encontrar clicando aqui ou aqui.

O código abaixo é um exemplo de como utilizar o plugin.

module.controller('MyCtrl', function($cordovaBadge) {
    $cordovaBadge.set(3).then(function() {
        //sucess
    }, function(error) {
        console.error(error);
  });
});

Criando os arquivos do app

A primeira coisa a ser feita é criar o modulo angular adicionando as dependências do Ionic e do ngCordova.

var app = angular.module('todo', ['ionic', 'ngCordova'])
.run(function($ionicPlatform, DbFactory) {
  $ionicPlatform.ready(function() {
    if(window.cordova && window.cordova.plugins.Keyboard) {      
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);      
      cordova.plugins.Keyboard.disableScroll(true);
    }
    if(window.StatusBar) {
      StatusBar.styleDefault();
    }
  });
});

Agora vamos definir toadas as rotas do app.

app.config(function($stateProvider, $urlRouterProvider) {
  $stateProvider    
    .state('home', {
      cache: false,
      url: '/home',
      templateUrl: 'templates/list.html',
      controller: 'ToDoListController'
    })    
    .state('new', {
      cache: false,
      url: '/new',
      templateUrl: 'templates/edit.html',
      controller: 'ToDoEditController'
    })
    .state('edit', {
      cache: false,
      url: '/edit/:id',
      templateUrl: 'templates/edit.html',
      controller: 'ToDoEditController'
    });

    $urlRouterProvider.otherwise('/home')
});

Vamos criar um factory para encapsular os acessos ao banco de dados.

app.factory('DbFactory', function ($ionicPlatform, $cordovaSQLite, $rootScope) {
     return {
          /**
           * Inicializa a conexação com o banco de dados
           */
          initialize: function () {
               if (!$rootScope.db) {
                    //abrindo a conexão com o banco de dados
                    //caso ele não exista ele será criado                    
                    var db = $cordovaSQLite.openDB({ name: "ToDo.db", iosDatabaseLocation: 'Default' });

                    //salvando o db em uma variavel global para não ter que carregar toda vez
                    $rootScope.db = db;

                    //sql para criar a tabela
                    var createTable = "CREATE TABLE IF NOT EXISTS ToDo ( " +
                         "id integer primary key autoincrement, " +
                         "title varchar(50) not null, " +
                         "description varchar(250) not null," +
                         "done integer not null)";

                    //criando a tabela onde serão gravados os registros
                    $cordovaSQLite.execute(db, createTable).then(function (res) {
                         console.log("Tabela criada");
                    }, function (error) {
                         console.error("Ocorreu algum erro ao criar a tabela. " + error);
                    });
               }
          },
          /**
           * Recupera dados do banco de dados
           */
          getData: function (query, whereParam) {
               this.initialize();
               return $cordovaSQLite.execute($rootScope.db, query, whereParam)
                    .then(function (result) {
                         return { success: true, rows: result.rows };
                    }, function (error) {
                         return { success: false, message: error };
                    });
          },
          /**
           * Salva dados no banco de dados
           */
          saveData: function (query, args) {
               this.initialize();
               return $cordovaSQLite.execute($rootScope.db, query, args)
                    .then(function (result) {
                         return { success: true, id: result.insertId, rowsAffected: result.rowsAffected };
                    }, function (error) {
                         return { success: false, message: error };
                    });
          }
     }
});

Vamos criar um factory para fazer o CRUD de uma tarefa.

app.factory('ToDoFactory', function (DbFactory) {
     return {
          /**
           * Busca todas as tarefas do banco de dados
           */
          all: function () {               
               return DbFactory.getData("select * from ToDo order by title").then(function (result) {
                    var items = [];

                    if (result.success) {
                         for (var i = 0; i < result.rows.length; i++) {
                              var row = result.rows.item(i);
                              items.push(row);
                         }
                    }

                    return items;
               });               
          },
          /**
           * Salva uma tarefa no banco de dados
           */
          save: function (item) {
               var query = '';
               var args = [];

               if (item.id) {
                    query = 'update ToDo set title = ?, description = ? where id = ?';
                    args = [item.title, item.description, item.id]
               } else {
                    query = 'insert into ToDo (title, description, done) values (?,?,?)';
                    args = [item.title, item.description, 0];
               }

               return DbFactory.saveData(query, args).then(function (result) {
                    return result;
               });
          },
          /**
           * Deleta uma tarefa do banco de dados
           */
          delete: function(id) {
               return DbFactory.saveData('delete from ToDo where id = ?', [id]).then(function (result) {
                    return result;
               });
          },
          /**
           * Recupera uma tarefa do banco de dados
           */
          get: function (id) {
               return DbFactory.getData("select * from ToDo where id = ?", [id]).then(function (result) {
                    if (result.success && result.rows.length > 0) {
                         return result.rows.item(0);
                    }
               });
          },
          /**
           * Marca uma tarefa como concluída
           */
          done: function(id) {
               return DbFactory.saveData('update ToDo set done = ? where id = ?', [1, id]).then(function (result) {
                    return result;
               });
          },
          /**
           * Recupera a quantidade de tarefas não concluídas
           */
          countNotDone: function() {
               return DbFactory.getData("select COUNT(*) as Qtd from ToDo where done = ?", [0]).then(function (result) {
                    var count = 0;
                    if (result.success) {
                         count = result.rows.item(0).Qtd;
                    }
                    return count;
               });
          }
     }
});

Agora vamos criar um controller para listar as tarefas.

app.controller('ToDoListController', function ($scope, $ionicPlatform, $ionicListDelegate, $cordovaToast, $cordovaBadge, ToDoFactory) {
     //utilizando a função $ionicPlatform.ready para verificar se o dispositivo (celular, tablet, ...)
     //foi carregado completamente.
     //Esse teste é importante sempre ao executar qualquer metodo de qualquer plugin do cordova
     $ionicPlatform.ready(function () {
          //buscando as tarefas cadastradas no banco
          ToDoFactory.all().then(function (result) {
               $scope.items = result;                
          });

          //setando o badge para o total de tarefas não concluídas
          ToDoFactory.countNotDone().then(function (count) { $cordovaBadge.set(count); });
     });

     //função para deletar uma tarefa do banco
     $scope.delete = function ($index, id) {
          $ionicPlatform.ready(function () {
               //deletando o registro do banco
               ToDoFactory.delete(id).then(function (result) {
                    //se deletou com sucesso
                    if (result.success) {
                         //remove o item da lista para não ter que recarregar toda a lista de novo
                         $scope.items.splice($index, 1);
                         $cordovaToast.showShortBottom('Tarefa excluída com suceso.');

                         //setando o badge para o total de tarefas não concluídas
                         ToDoFactory.countNotDone().then(function (count) { $cordovaBadge.set(count); });
                    } else { //se ocorreu algum erro para deletar
                         $cordovaToast.showShortBottom('Ocorreu um erro ao salvar a tarefa. Erro: ' + result.message);
                    }
               });
          });
     };

     //função para marcar uma tarefa como concluída
     $scope.done = function ($index, id) {
          $ionicPlatform.ready(function () {
               //marcando o registro como concluído no banco
               ToDoFactory.done(id).then(function (result) {
                    //se marcou com sucesso
                    if (result.success) {
                         //marco o item da lista como concluído para não ter que recarregar toda a lista de novo
                         $scope.items[$index].done = 1;
                         //fecha as opções abertas com o swipe na tela
                         $ionicListDelegate.closeOptionButtons();
                         $cordovaToast.showShortBottom('Tarefa concluída com suceso.');

                         //setando o badge para o total de tarefas não concluídas
                         ToDoFactory.countNotDone().then(function (count) { $cordovaBadge.set(count); });
                    } else { //se ocorreu algum erro para marcar como concluída                    
                         $cordovaToast.showShortBottom('Ocorreu um erro ao salvar a tarefa. Erro: ' + result.message);
                    }
               });
          });
     }
});

E agora um controller para criar/alterar uma tarefa.

app.controller('ToDoEditController', function ($scope, $state, $stateParams, $ionicPlatform, $cordovaToast,
     $cordovaLocalNotification, $cordovaBadge, ToDoFactory) {

     //inicializando o objeto que vai ser o modelo da tela de cadastro
     $scope.model = {};

     //caso seja informado um id, é porque está editando um registro
     if ($stateParams.id) {
          $ionicPlatform.ready(function () {
               //busca o registro no banco
               ToDoFactory.get($stateParams.id).then(function (item) {
                    //popula o objeto de modelo com os dados do banco
                    $scope.model = item;
               });
          });
     }

     //função para salvar uma tarefa no banco
     $scope.onSubmit = function () {
          $ionicPlatform.ready(function () {
               if ($scope.model) {
                    //salvando o registro no banco
                    ToDoFactory.save($scope.model)
                         .then(function (result) {
                              //se foi salvo com sucesso                    
                              if (result.success) {
                                   $cordovaToast.showShortBottom('Tarefa salva com suceso.');
                                   $state.go('home'); //voltando para a tela inicial

                                   //gerando uma notificação local
                                   $cordovaLocalNotification.schedule({
                                        id: result.id,
                                        title: 'Tarefa salva com sucesso.',
                                        text: 'Você tem uma tarefa para ser executada. Mãos a obra! =]'
                                   });

                                   //setando o badge para o total de tarefas não concluídas
                                   ToDoFactory.countNotDone().then(function (count) { $cordovaBadge.set(count); });
                              } else { //se ocorrer algum erro
                                   $cordovaToast.showShortBottom('Ocorreu um erro ao salvar a tarefa. Erro: ' + result.message);
                              }
                         });
               }
          });
     }
});

Por fim, vamos criar os templates das telas para listar e cadastrar as tarefas.

Tela de criar/alterar uma tarefa.

<ion-view title="New todo" class="bar-positive">
     <ion-content class="has-header padding">
          <form novalidate ng-submit="onSubmit();">
               <div class="list">
                    <label class="item item-input item-floating-label">
                         <span class="input-label">Titulo</span>
                         <input type="text" placeholder="Titulo" ng-model="model.title">
                    </label>
                    <label class="item item-input item-floating-label">
                         <span class="input-label">Descrição</span>
                         <textarea cols="5" rows="10" placeholder="Descrição" ng-model="model.description"></textarea>
                    </label>                    
               </div>

               <button class="button button-block button-positive" type="submit">
                    Salvar
               </button>
          </form>
     </ion-content>
</ion-view>

Tela para listar as tarefas.

<ion-view title="ToDo App" class="bar-positive">
     <ion-nav-buttons side="secondary">
          <button class="button icon ion-plus" ui-sref="new"></button>
     </ion-nav-buttons>

     <ion-content class="has-header padding">
          <ion-list can-swipe="true">
               <ion-item ng-repeat="item in items" ng-class="item.done == 1 ? 'todo-done' : ''">                    
                    <h2>{{ item.title }}</h2>
                    <p>{{ item.description }}</p>

                    <ion-option-button class="ion-checkmark button-balanced" ng-click="done($index, item.id)"></ion-option-button>
                    <ion-option-button class="ion-edit button-positive" ui-sref="edit({ id: item.id})"></ion-option-button>
                    <ion-option-button class="ion-trash-a button-assertive" ng-click="delete($index, item.id)"></ion-option-button>
               </ion-item>
          </ion-list>
     </ion-content>
</ion-view>

E por fim, o arquivo index.html

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
  <title></title>

  <link href="lib/ionic/css/ionic.css" rel="stylesheet">
  <link href="css/style.css" rel="stylesheet">

  <!-- ionic/angularjs js -->
  <script src="lib/ionic/js/ionic.bundle.js"></script>

  <!--ngCordova-->
  <script src="lib/ngCordova/dist/ng-cordova.js"></script>
  <!-- cordova script (this will be a 404 during development) -->
  <script src="cordova.js"></script>  

  <!-- your app's js -->
  <script src="js/app.js"></script>
  <script src="js/app.routes.js"></script>
  <script src="js/app.controllers.js"></script>
  <script src="js/app.services.js"></script>
</head>

<body ng-app="todo">
  <ion-nav-bar class="bar-positive">
    <ion-nav-back-button class="button-icon icon ion-arrow-left-b"> </ion-nav-back-button>
  </ion-nav-bar>
  <ion-nav-view></ion-nav-view>
</body>

</html>

Eu espero que esse tutorial tenha sido útil para você. Qualquer dúvida deixe seu comentário abaixo.

O código fonte de exemplo está disponível no GitHub.