この記事はand factoryの2024年アドベントカレンダーの記事です。
Flutterでアプリ開発している渡邉です。
年末も近づいてきて、Flutterのプロジェクトも大掃除(リファクタリング)したいところです。
この度、DevToolsでアプリのリソースを確認したところアプリ内の画像リソースが多くを占めていることが分かりましたので、どのリソースが実質使われていないのかを特定してみました。
DevToolsではAppSizeタブがありサイズ情報を確認することができます。詳しくは公式のドキュメントをご参照ください。
開発中のプロジェクトでは、以下のような理由でアプリ内の不要な画像リソースが残っていることがあります。
- 古いUIで使用されていた画像が未削除
- 機能のリファクタリング後に使用されなくなった画像
- テスト用リソースが残存
今回は、Flutterプロジェクトで flutter_gen
を使って生成された画像リソースの中から、未使用の画像リソースを効率的に抽出する方法を紹介します。
flutter_gen
とは?
flutter_gen
は、Flutterプロジェクトで定義されたアセット(画像やフォントなど)を安全に利用するための自動生成ツールです。pubspec.yaml
に記述されたアセット情報から、型安全なDartコードを生成します。
例えば、以下のようなコードを生成します:
/// File path: assets/images/sample_image.webp
AssetGenImage get sampleImage =>
const AssetGenImage('assets/images/sample_image.webp');
これにより、手動でファイルパスを記述する手間を省き、コードの保守性を高めます。
未使用リソースを抽出するスクリプト
以下は、flutter_gen
が生成したコードを解析し、未使用の画像リソースを抽出するDartスクリプトです。
このスクリプトは以下の手順で動作します:
-
flutter_gen
が生成したファイル(通常はlib/gen/assets.gen.dart
)を解析して、全てのアセット情報を取得 - プロジェクト全体をスキャンし、各アセットがどのファイルで使用されているか記録する
- 未使用のアセットをリストアップ
コードが汚かったのでChatGPTに整形していただきました。
import 'dart:io';
void main() {
// Generatedアセットファイルを指定
final assetsFile = File('lib/gen/assets.gen.dart');
// ファイルが存在しない場合のエラーチェック
if (!assetsFile.existsSync()) {
print('assets.gen.dart が見つかりません。パスを確認してください。');
return;
}
// assets.gen.dart 内のゲッターメソッド名とアセットパスを取得
final getterNames = <String>[]; // 生成されるゲッター名
final assetPaths = <String, String>{}; // {getterName: filePath}
final lines = assetsFile.readAsLinesSync();
for (var i = 0; i < lines.length; i++) {
// ファイルパスを取得
final filePathMatch =
RegExp(r'/// File path: (.+)').firstMatch(lines[i]);
if (filePathMatch != null && i + 1 < lines.length) {
final filePath = filePathMatch.group(1)!;
// 次の行でゲッターメソッドを検索
final getterMatch =
RegExp(r'AssetGenImage get ([a-zA-Z0-9_]+) =>').firstMatch(lines[i + 1]);
if (getterMatch != null) {
final getterName = getterMatch.group(1)!;
getterNames.add(getterName);
assetPaths[getterName] = filePath;
}
}
}
if (getterNames.isEmpty) {
print('アセットが見つかりませんでした。');
return;
}
// プロジェクト全体で使用されているアセットゲッターを検索
final projectFiles = Directory('./lib').listSync(recursive: true);
final usageDetails = <String, List<String>>{}; // {getterName: [filePaths]}
for (var getter in getterNames) {
usageDetails[getter] = []; // 初期化
}
for (var file in projectFiles) {
if (file is File && file.path.endsWith('.dart')) {
// ./lib/gen/assets.gen.dart を除外
if (file.path.contains('lib/gen/assets.gen.dart')) {
continue;
}
final content = file.readAsLinesSync();
for (var line in content) {
// コメント行をスキップ
if (line.trim().startsWith('//')) {
continue;
}
for (var getter in getterNames) {
if (line.contains(getter)) {
usageDetails[getter]!.add(file.path);
}
}
}
}
}
// 使用状況をログに出力
print('\nアセット使用状況 (使用回数順):');
final sortedUsageDetails = usageDetails.entries.toList()
..sort((a, b) => b.value.length.compareTo(a.value.length)); // 使用回数でソート
for (var entry in sortedUsageDetails) {
final getter = entry.key;
final usageCount = entry.value.length;
print('${getter} (${assetPaths[getter]}): $usageCount回使用');
if (usageCount > 0) {
print(' 使用ファイル:');
for (var path in entry.value) {
print(' - $path');
}
}
}
// 未使用アセットを特定
final unusedAssets = sortedUsageDetails.where((entry) => entry.value.isEmpty);
if (unusedAssets.isEmpty) {
print('\nすべてのアセットが使用されています。');
} else {
print('\n未使用のアセット:');
for (var entry in unusedAssets) {
print('${entry.key}: ${assetPaths[entry.key]}');
}
}
}
スクリプトの実行方法
- プロジェクトのルートディレクトリにスクリプトを保存(例:
check_unused_assets.dart
) - 以下のコマンドで実行します
dart check_unused_assets.dart
出力例
以下はスクリプトを実行した際のサンプル出力です。
アセット使用状況
アセット使用状況 (使用回数順):
imgSample (assets/gifs/img_sample.webp): 3回使用
使用ファイル:
- lib/screens/home_screen.dart
- lib/screens/my_page_screen.dart
- lib/widgets/header.dart
anotherImage (assets/images/another_image.webp): 0回使用
未使用アセット
未使用のアセット:
anotherImage: assets/images/another_image.webp
VSCodeのターミナルからCmd+クリックでファイルに飛べますのでサクサク作業できます。
まとめ
このスクリプトを使うことで、flutter_gen
で管理されている画像リソースの中から未使用のものを効率的に見つけることができます。未使用リソースを削除することで、アプリのビルドサイズを削減し、パフォーマンスの向上が期待できます。
年末の大掃除の一環として、ぜひ活用してみてください