cordovaSQLite使用上の落とし穴とその対策

ionicハイブリッドアプリ開発 ionic
ionicハイブリッドアプリ開発

$cordovaSQLiteでエラー

ngCordovaはモバイルアプリ開発において、スマートフォン本体が持つハードウェアの機能にアクセスできる橋渡しをしてくれる機能がたくさん提供されているプラグイン集です。
その中の1つに、cordovaSQLiteがあります。
例えばハイブリッドアプリでありながら「カメラ」機能にアクセスしたり、GPS機能や重力傾きセンサーを使うことができるようになります。
しかもHTML上で。つまりホームページを軽く作れるレベルで、カメラやGPS機能が使えるようになる夢のプラグイン群なのです。
今回はその実機機能の一つ、SQLiteのプラグインを使っている最中にエラーが発生しました

SQLiteのエラー症状と原因

エラーの症状としては、.service内で$cordovaSQLiteを呼び出してデータを取得後、戻り値が正常に受け取れなかったのです。
原因は、cordovaSQLiteは非同期で処理されるため、適切な処理をしないとダメだったのです。

CordovaSQLiteで失敗した例

var db = null;
// 〜〜中略 
// .runでデータベースの接続とか、初期化処理をします
.run(function($ionicPlatform , $cordovaSQLite) {
  $ionicPlatform.ready(function() {
    db = $cordovaSQLite.openDB("DBName");
  })
})


.service('GLOBAL', function ($ionicPlatform  ,$cordovaSQLite ) {
  this.getAll = function(){
    Data = [];
    $ionicPlatform.ready(function(){
      var query = "select * from data";
      $cordovaSQLite.execute(db, query).then(function(res) {
        for(i = 0 ; i <res.rows.length;i++){
          var obj = {
            id:res.rows.item(i).qid ,
            val:res.rows.item(i).val
          };
          Data.push(obj);
        }
      }, function (err) {
        console.error(err);
        alert("データの読み込みに失敗しました");
      });
    })
    return Data;
  }

SQLiteからデータを取得するserviceを作成して

.controller('sampleCtrl', function($scope , GLOBAL ) {
  $scope.Data = GLOBAL.getQuizAll();
  //〜その後$scope.Dataを使って足したり引いたり色々な処理をする
})

コントローラからSQLiteのデータを取得して、その後使用したいのだが・・・エラー。
console.logで$scope.Dataの内容を出力されると

[]

と表示される。つまり$scope.Dataの中身は空っぽなのです。

原因は、$cordovaSQLiteの処理は非同期で行われるため、$scope.Data = GLOBAL.getQuizAll();した時点ではまだQueryの結果が帰ってきていないために起こる模様。
例えば$scope.Dataをng-repeatで書き出すだけとかならエラーにならないのですが、取得した値を元に演算するとダメです。

CordovaSQLiteのトラブルを修正した例

よって、修正後はこのようになりました

//前略
.service('GLOBAL', function ($ionicPlatform  ,$cordovaSQLite , $q ) {
  this.getAll = function(){
    /* 非同期 */
    var deferred = $q.defer();
    Data = [];
    $ionicPlatform.ready(function(){
      var query = "select * from data";
      $cordovaSQLite.execute(db, query).then(function(res) {
        for(i = 0 ; i <res.rows.length;i++){
          var obj = {
            qid:res.rows.item(i).qid ,
            val:res.rows.item(i).val 
          };
          Data.push(obj);
        }
        deferred.resolve(Data);
      }, function (err) {
        console.error(err);
        deferred.reject("データの読み込みに失敗しました。");
      });
    })
    return deferred.promise;
  }
//後略〜

そして、呼び出し側は次の通り

.controller('sampleCtrl', function($scope , GLOBAL ) {
  GLOBAL.getAll().then(
    function(res){
      $scope.Data = res;
      //このあと、$scope.Dataを使って足したり、引いたり、色々したりします。
    }
  );
})

これで意図したとおりに動きました。