Monaca & OnsenUI でハイブリッドアプリ
OnsenUI = AngularJSなので、MonacaでAngularJSの形に従いながら、アプリケーションを作成している。
端末にファイルを保存&読み取りする場合、PhoneGap(Cordova) FileAPI のお世話になる。
HTML5 File API でファイルを扱う
ファイルを読み書きするだけならコールバック関数に仕事してもらって・・で良いんだけど、ファイルの中身をリスト形式で持たせて、画面に表示・・を使用とすると、表示したいタイミングでデータが取得出来てるわけではない非同期処理の壁に阻まれた。。
Promise でやってみる
そんな時はPromise!
AngularJS で Promise するには、$q.defer()
する。
基本パターン
function slowCall() {
var df = $q.defer();
// 非同期処理を担当する関数を定義
function fn() {
var msg = 'now : ' + new Date().getTime();
alert('inside async function.');
df.resolve(msg);
};
$timeout(fn, 1000);
return df.promise;
};
この、定義した slowCall
関数はPromiseを返してくれる。
非同期処理した後に、その結果を用いて更に処理を続けたい時は・・
呼び出し側
var testDeferSample = function () {
alert('test defer sample start.');
slowCall().then(function () {
alert('END!');
});
alert('Opps.. outside');
};
呼び出すとこんな感じ。
test defer sample start.
Opps.. outside
inside async function.
END!
非同期処理が無事終わった後のEND!
幾つも非同期呼び出しして、最後の最後になんか処理をはさみたい時
$q.all
を使う。
var testAllSample = function() {
alert('test sample start.');
var p1 = slowCall().then(function(msg) {
alert('LAST 1' + msg);
return msg;
});
var p2 = slowCall().then(function(msg) {
alert('LAST 2' + msg);
return msg;
});
$q.all([p1,p2]).then(function(res) {
alert('LAST MOST ' + res[0] + ', ' + res[1]);
});
};
呼び出すとこんな感じ
test all sample start.
inside async function.
LAST 1 1440643852941
inside async function.
LAST 2 1440643876515
LAST MOST 1440643852941, 1440643876515
p1, p2で返してきた値が最後のall呼び出しで受け取れる。
p1, p2で何も return
しない場合、 undefined が返ってくる。
resはallで呼び出した関数の数分のオブジェクトが入っている。
ファイル操作の実際
ファイルシステムを取得
var getFileSystem = function() {
var deferred = $q.defer();
window.requestFileSystem(window.PERSISTENT, 1024*1024, function(filesystem) {
deferred.resolve(filesystem);
});
return deferred.promise;
};
パスを指定してディレクトリを取得
var getTargetDirectory = function(dirPath) {
var deferred = $q.defer();
getFileSystem().then(function(filesystem) {
filesystem.root.getDirectory(dirPath, { create: true, exclusive: false },
function(dirEntry) {
deferred.resolve(dirEntry);
},
failFiles
);
});
return deferred.promise;
};
ディレクトリをオープンする時に、指定されたディレクトリがない場合、作成するオプションを渡している。
{ create: true, exclusive: false }
作成したくない場合、 { create: false }
にする。
failFiles
はディレクトリの取得に失敗した場合を扱うエラー処理関数。
こんな感じでエラーがあったことを表示してるだけ。
function failFiles(error) {
alert('failFiles' + error.code);
};
指定されたディレクトリにあるファイル名を一覧で返す
var listDir = function (dirPath) {
var deferred = $q.defer();
getTargetDirectory(dirPath).then(function (dirEntry) {
var reader = dirEntry.createReader();
var fileNames = [];
reader.readEntries(
function (entries) {
for (var i = 0; i < entries.length; i++) {
fileNames.push(entries[i].name);
}
deferred.resolve(fileNames);
},
function (error) {
console.log("reading target directory error");
}
);
});
return deferred.promise;
};
ファイルの中身を読む場合
var readFile = function(dirPath, fileName) {
var deferred = $q.defer();
getTargetDirectory(dirPath).then(function(dirEntry) {
dirEntry.getFile(fileName, { create: false },
function(fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function() {
deferred.resolve(this.result);
};
reader.readAsText(file);
});
},
function(error) {
console.log("reading target directory error");
}
);
});
return deferred.promise;
};
ファイルを保存する場合
var _writeFile = function(dirPath, fileName, fileText) {
var deferred = $q.defer();
getTargetDirectory(dirPath).then(function(dirEntry) {
dirEntry.getFile(fileName, { create: true },
function(fileEntry) {
fileEntry.createWriter(
function(writer) {
var cb = function() {
//console.log("write end");
alert("保存しました。");
deferred.resolve(fileName);
}
writer.onwrite = cb;
writer.onerror = function() { console.log("保存に失敗しました。"); }
//console.log( fileText );
writer.write( fileText );
} ,
function() {
console.log("create writer error");
}
);
},
failFiles
);
});
return deferred.promise;
};
ディレクトリを指定したら、ファイル名とファイルの中身をJSONにして返して欲しい
ディレクトリパスとファイル名を指定されたら、JSONオブジェクトにして返すPromise.
var createJsonObject = function (targetDirPath, fileName) {
var deferred = $q.defer();
readFile(targetDirPath, fileName).then(function (content) {
var json = { name: fileName, data: content };
deferred.resolve(json);
});
return deferred.promise;
};
後でJSONオブジェクトを$q.all()呼び出しするために、関数を生成する関数をリストで保持する
var createCallbackFunctionList = function (targetDirPath, fileNames) {
var deferred = $q.defer();
var functions = [];
fileNames.map(function (fileName) {
functions.push(createJsonObject(targetDirPath, fileName));
});
deferred.resolve(functions);
return deferred.promise;
};
本体
前提:ファイル名を保存時に new Date().getTime()
で作ってる。
ゴール:ファイル名の降順に並び替えしてデータを取得
var getFileAsJsonList = function () {
var jsonList = [];
var targetDirPath = 'YOUR_TARGET/DIRECTORY/HERE';
var deferred = $q.defer();
// まずディレクトリにあるファイル名を元に、JSONオブジェクトを取ってくる関数を作る
FileUtilService.listDir(targetDirPath).then(function (fileNames) {
createCallbackFunctionList(targetDirPath, fileNames).then(function (fns) {
deferred.resolve(fns);
})
});
// ファイルコンテンツ取得時に非同期処理のため、ファイルの順序がバラバラになる
// 全ファイルコンテンツの取得後に、ファイル名(日時)で並べ替え。
deferred.promise.then(function (promises) {
// 全部実行したい関数の配列を取得 then の後にくる関数は、
// promisesが全て実行し終わったら一番最後に実行される
$q.all(promises).then(function (photos) {
jsonList = photos.sort(function (a, b) {
return parseInt(b.name) - parseInt(a.name);
});
alert('got' + jsonList.length);
angular.forEach(jsonList, function (value, key) {
console.log('FileInfo : ' + value.name + ', content length=' + value.data.length);
})
});
});
};