flutterでアプリを再起動しても過去データが消えないようにするにはどうすればよいでしょうか。
色々な方がローカルへのアクセス方法やjsonの書き込み方法といったものをまとめておられますが、複雑なものをやろうとするとやや面倒だったりします。
今回はserializeを使わずに、リスト形式のjsonのやり取りについてこの記事でまとめていければと思います。
json形式のflutterへのマッピング
例えば、以下のようなjsonを考えます
{[
{
name: "田中太郎",
age: 70,
gender: "男性",
},
{
name: "田中次郎",
age: 60,
gender: "男性",
}
]}
複数の同じ構造体のデータを保存したいことがあることは往々にしてありますが、flutterでは少し工夫をする必要があります。
以下、2つの項目に分けてそれぞれこれらの形式のデータを読み書きできるプログラムを書いていきたいと思います。
- 構造体をjsonとやり取りする
- リスト形式のデータをjsonとやり取りする
構造体をjsonとやり取りする
flutterでは構造体と名前のつくstructみたいなものはなく、基本的にClassを使います。
class Person {
Person({this.name, this.score, this.age, this.gender, this.date});
String name;
int age;
String gender;
}
このようにすることで、
Person person = Person(
name: "田中太郎",
age: 70,
gender: "男性",
);
みたいにすれば呼び出すことができます。
しかし、jsonとやり取りをする場合は実はこれでは不十分で、jsonに対してclassの各要素のマッピングを行う必要があります。以下がそれぞれでマッピングを行った場合のPersonクラスの定義になります
class Person {
Person({this.name, this.score, this.age, this.gender, this.date});
String name;
int age;
String gender;
factory Person.fromJson(Map<String, dynamic> json) {
return Person(
name: json['name'] as String,
age: json['age'] as int,
gender: json['gender'] as String,
);
}
Map<String, dynamic> toJson() => {
'name': name,
'age': age,
'gender': gender,
};
}
このようにすることで、Class内の変数を各jsonの形式にマッピングすることが可能となります。
リスト形式のデータをjsonとやり取りする
ここで、クラスからjsonへの書き込み・及びクラスへの読み込みについて考えたいと思います。
ローカル保存先はpath_providerのgetApplicationDocumentsDirectory()を使って取得します。
class PersonStorage {
PersonStorage(this.filePath);
String filePath;
// read path
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
// get file
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/$filePath');
}
// convert from json to class
List<History> parseEntries(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Person>((json) => Person.fromJson(json)).toList();
}
// get as json
Future<List<Person>> readEntries() async {
try {
final file = await _localFile;
// Read the file
String contents = await file.readAsString();
List<Person> personList = parseEntries(contents);
return personList;
} catch (e) {
// If we encounter an error, return []
return [];
}
}
// write
Future<File> writeEntries(List<Person> personData) async {
final file = await _localFile;
// convert from list to json
List<Map> encoder = [];
for (var trial in personData) {
encoder.add(trial.toJson());
}
// Write the file
return file.writeAsString(json.encode(encoder));
}
}
リスト形式でやり取りする場合にポイントとなるのがreadEntries・writeEntriesとなります。
readEntriesではjsonをparseして、それをクラスPersonのList形式にマッピングすることで読み込むことができます。リストとして使うために最後にtoListを使うことが必要になります。
またwriteEntriesについては、それぞれfor文で各Classを取り出し、json形式に変換してからListに詰め替える処理をしています(もしかしたらもう少しうまい方法があるかもしれません)。
これにより、List形式のデータを一括で読み込み/書き込みが可能になります。
利用方法
最後に、これらの関数の使い方について説明します。
これらをjsonとして読み込む場合は下記のように初期化します
String personPath = 'persons.json';
PersonStorage personStorage = new PersonStorage(personPath);
List<Person> personList = [];
このpersons.jsonが保存先のpathとなります。
読み込みの場合は、Widgetの中で下記の関数を呼ぶ必要があります。
下のケースではStatefulWidgetを想定しているのでsetStateを呼んでいますが、必ずしも必要はないはずです。
personStorage.readEntries().then((List<Person> value) {
setState(() {
personList = value;
});
});
書き込みは新しいpersonListをwriteEntriesに与えてあげるだけです。
personStorage.writeEntries(personList);
まとめ
flutterでList形式のjsonの取扱いを説明しました。
本来だとserializeとか使うのが王道なのかもしれませんが、この形式だとほぼどこでも使い回せるかつ、個人的には扱いやすかったので選択肢の一つとして悪くはないのかなーと思います。