Flutterのマルチパッケージの中でローカルのJSONを読み込みたい!
はじめに
Flutterのマルチパッケージプロジェクトでは、アセット管理、特にローカルJSONファイルの読み込みが課題となることがあります。
通常のシングルパッケージプロジェクトとは異なるアプローチが必要となり、開発者を悩ませることがあります。
この記事では、Flutterのマルチパッケージプロジェクトにおいて、ローカルのJSONファイルを効果的に読み込む方法について詳しく解説します。
この記事は KINTOテクノロジーズアドベントカレンダー2024 の23日目の記事です🎅🎄
今回用意したテストプロジェクト
今回、調査のため、マルチパッケージで管理する簡潔なプロジェクトを用意しました。
このプロジェクトは、以下のような構成になっています。
🎯 Dart SDK: 3.5.4
🪽 Flutter SDK: 3.24.5
🧰 melos: 6.2.0
├── .github
├── .vscode
├── app/
│ ├── android/
│ ├── ios/
│ ├── lib/
│ │ └── main.dart
│ └── pubspec.yaml
├── packages/
│ ├── features/
│ │ ├── assets/
│ │ │ └── sample.json
│ │ ├── lib/
│ │ │ └── package1.dart
│ │ └── pubspec.yaml
│ ├── .../
├── analysis_options.yaml
├── melos.yaml
├── pubspec.yaml
└── README.md
Assetにあるファイルを読み込む
一般的なシングルパッケージでのAssetの読み込みは、以下のようにすればできるという説明はよく見かけます。
flutter:
assets:
- assets/ # アセットフォルダを指定
import 'package:flutter/services.dart' show rootBundle;
Future<String> loadAsset() async {
return await rootBundle.loadString('assets/sample.json');
}
公式も同じ様な説明をしています。
実際にAssetからJSONファイルを読み込んでTextにString表示するだけのWidgetを作ってみました。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
class LocalAssetPage extends StatefulWidget {
const LocalAssetPage({super.key});
LocalAssetPageState createState() => LocalAssetPageState();
}
class LocalAssetPageState extends State<LocalAssetPage> {
String _jsonContent = '';
void initState() {
super.initState();
_loadJson();
}
Future<void> _loadJson() async {
final response = await rootBundle.loadString('assets/sample.json');
final data = await json.decode(response);
setState(() {
_jsonContent = json.encode(data);
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Local Asset Page'),
),
body: Center(
child: _jsonContent.isEmpty
? const CircularProgressIndicator()
: Text(_jsonContent),
),
);
}
}
しかし、マルチパッケージでのAssetの読み込みは、この方法ではうまくいきません。
🤨
flutter_genを使ってAssetを読み込む
大抵の場合、マルチパッケージでのAssetの読み込みは、flutter_genを使って解決できます。
flutter_genは、AssetやLocalization等のパスからコード生成をして、タイプセーフにアセットの読み込みが実現できるツールで、マルチパッケージのAssetの読み込みもサポートしています。
flutter_genでマルチパッケージのAssetを読み込むには、以下の設定が必要になります。
flutter_gen:
assets:
outputs:
package_parameter_enabled: true
この設定を入れて、flutter_genを実行すると、マルチパッケージのAssetを読み込むためのコードが生成されます。
そこで生成されたコードを使ってタイプセーフにAssetを読み込むことができます。
上記の例をflutter_genを使って書き換えると、以下のようになります。
import 'package:{YOUR_PACKAGE_NAME}/gen/assets.gen.dart';
Future<String> loadAsset() async {
return await rootBundle.loadString(Assets.sample);
}
実際に以下の様なコードを書くことで、マルチパッケージのAssetを読み込むことができます。
import 'dart:convert';
+ import 'package:feature_flutter_gen_sample/gen/assets.gen.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
class FlutterGenSamplePage extends StatefulWidget {
const FlutterGenSamplePage({super.key});
FlutterGenSamplePageState createState() => FlutterGenSamplePageState();
}
class FlutterGenSamplePageState extends State<FlutterGenSamplePage> {
String _jsonContent = '';
void initState() {
super.initState();
_loadJson();
}
Future<void> _loadJson() async {
+ final response = await rootBundle.loadString(Assets.sample);
- final response = await rootBundle.loadString('assets/sample.json');
final data = await json.decode(response);
setState(() {
_jsonContent = data.toString();
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('FlutterGen Sample'),
),
body: Center(
child: _jsonContent.isNotEmpty
? Text(_jsonContent)
: const CircularProgressIndicator(),
),
);
}
}
ファイルのパスが構造化されるので、とてもキレイですね!
チーム開発のAsset管理はこれで安心です。
ですが、なるべくツールに頼らない方法を取りたい場合も多々ありますよね?
次はその方法についてもお話します。
flutter_genを使わないでAssetを読み込む
flutter_genを使わずにマルチパッケージのAssetを読み込む方法ももちろんあります。
その場合は、以下のルールでパスを指定することでAssetを読み込むことができます。
- パッケージ名は、そのassetが格納されているPackageのpubspec.yamlのnameに指定した名前になります。
- フォルダパスは、そのPackageのpubspec.yamlのassetsに指定したパスになります。
name: local_asset
...
flutter:
assets:
- assets/
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
class LocalAssetPage extends StatefulWidget {
const LocalAssetPage({super.key});
LocalAssetPageState createState() => LocalAssetPageState();
}
class LocalAssetPageState extends State<LocalAssetPage> {
String _jsonContent = '';
void initState() {
super.initState();
_loadJson();
}
Future<void> _loadJson() async {
+ final response = await rootBundle.loadString('packages/local_asset/assets/sample.json');
- final response = await rootBundle.loadString('assets/sample.json');
final data = await json.decode(response);
setState(() {
_jsonContent = json.encode(data);
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Local Asset Page'),
),
body: Center(
child: _jsonContent.isEmpty
? const CircularProgressIndicator()
: Text(_jsonContent),
),
);
}
}
このようにして、flutter_genを使わずにマルチパッケージのAssetを読み込むことができます。
小規模プロジェクトや、個人開発ではこの方法でも十分に対応できそうです。
このパスの作成ルールが理解できておらず、ファイルの相対パスを指定したり、パスの設定を色々工夫してみたりしましたが、
そもそもエラーでビルドできなかったりととても苦戦しました...
pubspec.yamlで指定するパスについて
ローカルでアセットを管理する時に、pubspec.yamlで指定するパスについても注意が必要です。
assetsのパスは、pubspec.yamlからの相対パスで指定しますが、
/assets
と/assets/{サブフォルダ}
の扱いにも注意が必要です。
以下の様にJSONファイルをサブフォルダに移動した場合、
├── packages/
│ ├── features/
│ │ ├── assets/
│ │ │ └── jsons/
│ │ │ └── sample.json <<< HERE
│ │ ├── lib/
│ │ │ └── package1.dart
│ │ └── pubspec.yaml
│ ├── .../
僕はてっきり /assets
と指定すれば、/assets/{サブフォルダ}
以下のファイルも読み込めると思っていましたが、
loadStringのパスを packages/local_asset/assets/jsons/sample.json
に変更しても読み込めなくなります。
サブフォルダに移動した場合は、以下の様にサブフォルダを明示的に指定する必要があります。
flutter:
assets:
- /assets/jsons/
これで、packages/local_asset/assets/jsons/sample.json
をロードできる様になりました。
サブフォルダで細かくアセットを管理するのであれば、サブフォルダの指定もpubspec.yamlに追加するのを忘れない様にしないといけません。
ちなみに、ここまで assets
フォルダで話をしていましたが、このフォルダ名も変更できます。
pubspec.yamlと実際のパス構成が合っていれば、 assets
フォルダ以外でも問題なく読み込めます。
まとめ
今回はFlutterのマルチパッケージの中でローカルのJSONを読み込む方法についてお話しました。
普段はiOSの開発がメインなので、簡単にできるだろうと思っていたのですが、意外と苦労しました。
マルチパッケージ下での開発手法はまだあまり情報がない様なので、今後もこういった情報があれば共有していきたいと思います。
関連記事 | Related Posts
We are hiring!
【PdM】my route開発G/東京
my route開発グループについてmy route開発グループは、my routeに関わる開発・運用に取り組んでいます。my routeの概要 my routeは、移動需要を創出するために「魅力ある地域情報の発信」、「最適な移動手段の提案」、「交通機関や施設利用のスムーズな予約・決済」をワンストップで提供する、スマートフォン向けマルチモーダルモビリティサービスです。
プロダクトデザイナー/my route開発G/東京
my route開発グループについてmy route開発グループは、my routeに関わる開発・運用に取り組んでいます。my routeの概要 my routeは、移動需要を創出するために「魅力ある地域情報の発信」、「最適な移動手段の提案」、「交通機関や施設利用のスムーズな予約・決済」をワンストップで提供する、スマートフォン向けマルチモーダルモビリティサービスです。