【Flutter】sqfliteパッケージによるDBファイルの配置場所について、もう少し考えてみる
「何」を考えるのか
sqfliteは、FlutterでSQLiteを利用する際によく用いられるパッケージです。
SQLiteで管理するデータは、「〇〇.db」のようなファイル名で、端末内に保存されることになります。
この「〇〇.db」のファイルを、端末のどこに配置したらいいのか、について今回、考えたいと思います。
後々、コード例を2パターン書きましたが、あくまで一例としてみていただけると幸いです。
長々と説明ありますが、知って欲しいと思っていることは、
iOSの場合、sqfliteに用意されているgetDatabasePathには注意が必要(使わない選択肢がドキュメントに言及されている)
ということだけです。
書くこと・書かないこと
- 書くこと
配置場所に関する話(オンリー) - 書かないこと
sqfliteの詳しい使い方
こちらはドキュメント等、ご参照ください。
要件
配置場所の要件は、セキュリティを担保するため(外から悪さができないよう)
外部から(ユーザーからも他のアプリからも)アクセスできない場所
とします。
外部からアクセスできるようにする場合とは記述方法が異なるのでご注意ください。(私には経験のないパターンです)
sqfliteに用意されている「getDatabasePath」について
sqfliteには「getDatabasePath」というそれらしいメソッドが用意されています。sqfliteの使い方を説明した記事でも、このメソッドを使ったものが多い印象です。
では「getDatabasePath」では、どこのパスを取得することになるのでしょうか。
「getDatabasePath」のドキュメントをみてみると、以下のように説明されています。
Get the default databases location.
On Android, it is typically data/data/
On iOS and MacOS, it is the Documents directory.
Note for iOS: Using path_provider is recommended to get the databases directory. The most appropriate location on iOS would be the Library directory that you could get from thepath_provider
package (https://pub.dev/documentation/path_provider/latest/path_provider/getLibraryDirectory.html).
一行目で「デフォルトのデータベースの場所を取得する」とあり、AndroidとiOSのそれぞれの説明が続きます。
そう、特筆すべきは、AndroidとiOSで挙動が異なる ことです。
プラットフォーム別に挙動が異なるので、以下プラットフォームごとに考えていきます。
Androidの場合
「getDatabasePath」の話をする前に、
DBファイルをどこに保存すべきか、Androidでは「セキュアコーディングガイド」というものに記述があったります。
参考)
2020-11-01の方はWebサイトで閲覧できますが、リンク切れになるかもしれません。
引用すると
Context#getDatabasePath(String name)、もしくはContext#getFilesDirで取得できるディレクトリに配置する
とのこと。
上記メソッドから返却されるパスは外部からアクセスできない場所であり、要件を満たします。
さて「getDatabasePath」のAndroidの説明に戻ると
On Android, it is typically data/data/
とあります。
実はこれContext#getDatabasePath(String name)
の結果を返しています。
GithubのコードさかのぼっていくとonGetDatabasesPathCall
といメソッドに行き着き、そこでContext#getDatabasePath(String name)
を呼び出していることがわかります。
Githubで該当メソッドが書かれているファイル
ちょっと不親切かもしれませんが、詳しいたどり方についてはここでは書きません。(あまり書くのもよくない気がするので)
ネイティブのどこの処理を実行しているかは、以下を参考にたどりました。
Context#getDatabasePath(String name)が呼び出されているということなので、セキュアコーディングガイドを満たしています。
AndroidではgetDatabasePath
を使っても問題なさそうです。
iOSの場合
iOSの場合、「getDatabasePath」のドキュメントに詳細な説明は記載されています。
On iOS and MacOS, it is the Documents directory.
Note for iOS: Using path_provider is recommended to get the databases directory. The most appropriate location on iOS would be the Library directory that you could get from thepath_provider
package (https://pub.dev/documentation/path_provider/latest/path_provider/getLibraryDirectory.html).
まず、getDatabasePathでは「ドキュメントディレクトリ」を返すとのこと。
このドキュメントディレクトリはユーザのアクセスを許すこともできるので、今回の要件では不適切だと言えます。
各ディレクトリの説明)
よって、ドキュメントで薦められている通り、path_provider
のgetLibraryDirectory
を利用するのがいいかと思います。
(コード例は後で)
getLibraryDirectory
はライブラリディレクトリを返し、ライブラリディレクトリはドキュメントにあるように、ユーザや他のアプリからアクセスすることはできません。
また、ライブラリディレクトリ内であれば、自動でバックアップも取ってくれると説明では書いてあります。
path_providerを使った別の選択肢
path_provider
を使うのであれば、別の選択肢も生まれてくるでしょう。
スマートフォンアプリで関わるディレクトリ&内部ストレージのものに限定すると以下のメソッドたちが考えられます。
(ドキュメントの日本語訳をざっくりのせただけなので、正確な情報を確認したい場合はドキュメントを参照ください)
メソッド名 | 説明 |
---|---|
getApplicationDocumentsDirectory | ユーザーが作成したデータや、アプリケーションでは再現できないデータをアプリケーションが置くことができるディレクトリへのパス。 Androidの利用API : getDataDirectory API, iOSの利用API : NSDocumentDirectory API |
getApplicationSupportDirectory | アプリケーションがアプリケーションサポートファイルを置くことができるディレクトリへのパス。ユーザーに公開したくないファイルに使用してください。アプリでは、このディレクトリをユーザーデータファイルに使用してはいけません。 Androidの利用API : getFilesDir API, iOSの利用API : NSApplicationSupportDirectory API |
getLibraryDirectory | sqlite.dbなど、永続的にバックアップされ、ユーザーには表示されないファイルをアプリケーションが保存するためのディレクトリへのパス。Androidでは、同等のパスが存在しません。 iOSの利用API : NSLibraryDirectory API ※ソースコードたどりました |
getTemporaryDirectory | バックアップされていない、ダウンロードしたファイルのキャッシュを保存するのに適したデバイス上の一時ディレクトリへのパス。このディレクトリのファイルは、いつでも消去することができます。 Androidの利用API : getCacheDir API, iOSの利用API : NSCachesDirectory API |
getApplicationDocumentsDirectory
はAndroidもiOSもユーザからアクセスできる場所を提供するので不適切です。
またgetTemporaryDirectory
も名前の通り永続化できないディレクトリであり、DBファイルを保存するべきではありません。
よって、path_provider
で要件を満たすのは
Android
getApplicationSupportDirectory
(getFilesDirを利用している)
iOS
getApplicationSupportDirectory
getLibraryDirectory
考えられるコード例
Android・iOS両プラットフォームにリリースするアプリだとして書きます。
データベースを開く(なければ作成する)処理
import 'package:sqflite/sqflite.dart';
// データベースを開く(なければ作成する)処理のみ
Future<Database> getDatabase() async {
// getDbPathは自作メソッド
final path = await getDbPath();
final db = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
await db.execute('CREATE TABLE ..省略..');
});
return db;
}
getDbPath
の中身が、今回述べた部分です。
以下に例として2パターン書きます。
①getDatabasePathのドキュメントの説明に従う場合
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
Future<String> getDbPath() async {
var dbFilePath = '';
if (Platform.isAndroid) {
// Androidであれば「getDatabasesPath」を利用
dbFilePath = await getDatabasesPath();
} else if (Platform.isIOS) {
// iOSであれば「getLibraryDirectory」を利用
final dbDirectory = await getLibraryDirectory();
dbFilePath = dbDirectory.path;
} else {
// プラットフォームが判別できない場合はExceptionをthrow
// 簡易的にExceptionをつかったが、自作Exceptionの方がよいと思う。
throw Exception('Unable to determine platform.');
}
// 配置場所のパスを作成して返却
final path = join(dbFilePath, 'sample.db');
return path;
}
Exceptionをthrowするかは意見が分かれるところかもしれません。
私は万一「ありえない場合」が起きたときにDBファイルの作成をしてほしくなかったので、上記のようにしました。
②プラットフォームごとに場合分けをしたくない場合
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
Future<String> getDbPath() async {
final dbDirectory = await getApplicationSupportDirectory();
final dbFilePath = dbDirectory.path;
final path = join(dbFilePath, 'sample.db');
return path;
}
path_provider
で両プラットフォーム条件を満たしたgetApplicationSupportDirectory
を使いました。
こちらの方が、記述としてはすっきりしますね。
もし、該当するディレクトリが返せない場合はgetApplicationSupportDirectory
内でExceptionがthrowされます。
まとめ
①getDatabasePathのドキュメントの説明に従う場合、
Androidはsqflite
のgetDatabasePath
を利用
iOSはpath_provider
のgetLibraryDirectory
を利用して、
プラットフォームごとに場合分けをします。
こちらの方が、各種ドキュメントで想定されるDBファイルの配置場所になります。
②プラットフォームごとに場合分けをしたくない場合
path_provider
のgetApplicationSupportDirectory
利用すれば要件を満たせます。
ここからは、個人的コメントですが、
②にするのはクロスプラットフォームの都合(path_provider
の仕様の都合)を押し付けている感があるように思います。
①だと、各種ドキュメントの後押しがありますが、「自然なDBファイル配置場所となる分、予測しやすいんじゃないか」と考えることもできます。
一例ですので、何を採用するかはおまかせします。
ただ最初に書いた通り、iOSの場合はsqfliteに用意されているgetDatabasePath使うのは避けた方がいい、と私は思います。
(path_provider
を使っていても、getApplicationDocumentsDirectory
使っている例も多いですが、私は避けた方がいいと思います)
最後の紹介
個人開発でFlutter製のアプリをリリースしました🎉🎉🎉🎉🎉🎉🎉
今回の話は、個人開発時に調べたこぼれ話でした。
Android
iOS
「一日一問」というアプリで、
「問いかけ」を記録しておいて、ランダム表示する「無理なく考えたいことを考える」アプリです。
良ければつかってみてください〜
使い方
以上です!
Discussion