マグネテック備忘録

Flutterアプリ開発の備忘録

【Flutter】JSON形式でローカルファイルにデータ保存(json_serializable)



データ保存について

  • ローカルデータの保存方法では、shared_preferencesが代表的です。
  • shared_preferencesは手軽に実装できる半面、リストを直接保存できないなどのデメリットがあります。また、keyは固有でなければならないのも個人的に面倒です(例:アリスとボブの名前と年齢を保存するときにkeyを「alice_age」、[bob_age]などとしなければならない)。
  • 一方、JSONでファイルに書き込んで保存すれば実装こそ少し手間がかかりますが、(1)リストも保存できる、(2)ファイルを分けて保存できるなどのメリットがあります。



使用するパッケージ

json_serializableとゆかいな仲間たち

  • json_serializableはクラス⇔JSON形式の変換をしてくれるツールです。
  • json_serializableと補助的な役割を持つbuild_runnerjson_annotationを使用します。
  • 本記事ではこれの詳しい解説はしないので、こちらのサイトなどを参考にしてください(本記事でも参考にしています)。

qiita.com


path_provider

  • プラットフォームに依存せずキャッシュなどアプリでよく使うディレクトリにアクセスするためのパッケージのこと⇒公式サイト
  • こちらについても詳しい使い方は以下のサイトなどを参考にしてください(本記事でもこちらを参考にしています)。

qiita.com


pubspec.yamlの編集

  • 以下のパッケージをpubspec.yamlに追加すればOKです(dev_dependenciesに追加するものもある点に注意)。

dependencies:
  json_annotation: ^4.8.1
  path_provider: ^2.1.2

dev_dependencies:
  build_runner: ^2.4.8
  json_serializable: ^6.7.1




保存&読み込みの手順

手順(1) 保存したいクラスを作成

  • save_data.dartを用意して、保存したい情報を変数にします。
  • この時点ではエラーが出ますが、後の手順を踏むことで消えるので問題ありません

import 'package:json_annotation/json_annotation.dart';
part 'save_data.g.dart'; // flutter pub run build_runner build --delete-conflicting-outputs(後述)で生成されるファイル名

@JsonSerializable()
class save_data {
  // セーブしたい情報を変数にする
  final int id;
  final String name;
  final List<int> nums;

  save_data({required this.id, required this.name, required this.nums});
  factory save_data.fromJson(Map<String, dynamic> json) => _$save_dataFromJson(json);
  Map<String, dynamic> toJson() => _$save_dataToJson(this);
}


上記ファイルを作成したら、

flutter pub run build_runner build --delete-conflicting-outputs

をターミナルで実行します。

実行には時間がかかりますが、終了すると以下のような'save_data.g.dartが生成され、先ほど「save_data.dart」に出ていたエラーが消えると思います。

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'save_data.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

save_data _$save_dataFromJson(Map<String, dynamic> json) => save_data(
      id: json['id'] as int,
      name: json['name'] as String,
      nums: (json['nums'] as List<dynamic>).map((e) => e as int).toList(),
    );

Map<String, dynamic> _$save_dataToJson(save_data instance) => <String, dynamic>{
      'id': instance.id,
      'name': instance.name,
      'nums': instance.nums,
    };



手順(2) セーブ

  • クラスを生成して、JSON形式で保存します(ファイル名はtest.jsonとした)。
  • path_providerのgetApplicationCacheDirectory()で保存するフォルダを取得します。
// クラス生成
final save_data = SaveData(id: 2, name: 'ペッパー', nums: [3, 5, 8]);
print('save_data = $save_data');

// セーブするファイル名を決定
final directory = await getApplicationCacheDirectory();
final path = '${directory.path}/test.json';
final file = File(path);
// print('出力パス = $path');

// String型に変換して保存
final jsonString = json.encode(save_data.toJson());
file.writeAsStringSync(jsonString);
// print('書き込み内容 = $jsonString');


実行結果

id = 2, name = ペッパー, nums = [3, 5, 8]


一応、test.jsonに書き込んだ内容は以下のようになります。

{"id":2,"name":"ペッパー","nums":[3,5,8]}



手順(3) ロード

  • JSONの文字列⇒Mapと変換することで、save_dataを復元できます。
// 参照するファイルを取得
final directory = await getApplicationCacheDirectory();
final path = directory.path;
final file = File('${path}/test.json');

// データ読み込み
final jsonString = file.readAsStringSync();
final save_data = SaveData.fromJson(jsonDecode(jsonString));
print('id = ${save_data.id}, name = ${save_data.name}, nums = ${save_data.nums}');


先ほどと同じ結果になるため、読み込めたことが確認できます。

{"id":2,"name":"ペッパー","nums":[3,5,8]}





サンプルコード

ソースコード

  • save_data.dart と save_data.g.dartは上記と同じなため省略
import 'package:flutter/material.dart';
import 'save_data.dart';
import 'dart:convert';
import 'dart:io';
import 'package:path_provider/path_provider.dart';

void main() {

  final app = MaterialApp(
    theme: ThemeData(
      colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      useMaterial3: true,
    ),
    home: const MyApp(),
  );

  runApp(app);
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  String output = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(

              // セーブボタン
              onPressed: () async{
                // クラス生成
                final save_data = SaveData(id: 2, name: 'ペッパー', nums: [3, 5, 8]);

                // セーブするファイル名を決定
                final directory = await getApplicationCacheDirectory();
                final path = '${directory.path}/test.json';
                final file = File(path);
                // print('出力パス = $path');

                // String型に変換して保存
                final jsonString = json.encode(save_data.toJson());
                file.writeAsStringSync(jsonString);
                // print('書き込み内容 = $jsonString');

                // 表示するテキスト
                setState(() {
                  output = 'セーブ(id = ${save_data.id}\n, name = ${save_data.name}\n, nums = ${save_data.nums})';
                });
              }, 
              child: const Text('Save'),
            ),

            // ロード
            ElevatedButton(
              onPressed: () async{
                // 参照するファイルを取得
                final directory = await getApplicationCacheDirectory();
                final path = directory.path;
                final file = File('${path}/test.json');

                // データ読み込み
                final jsonString = file.readAsStringSync();
                final save_data = SaveData.fromJson(jsonDecode(jsonString));

                // 表示するテキスト
                setState(() {
                  output = 'ロード(id = ${save_data.id}\n, name = ${save_data.name}\n, nums = ${save_data.nums})';
                });
              }, 
              child: const Text('load'),
            ),

            // outputを表示
            Text(
              'output\n$output',
              style: const TextStyle(
                fontSize: 30,
              ),
            )
          ],
        ),
      ),
    );
  }
}



実行結果

saveボタンでセーブ、loadボタンで保存したデータを読み込みます。

初期画面 saveを押した後 loadを押した後
初期画面 saveを押した後 loadを押した後