32
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TRIAL&RetailAIAdvent Calendar 2024

Day 9

flutter_genでアセット管理してみた

Last updated at Posted at 2024-12-07

はじめに

TRIAL&RetailAI Advent Calendar 2024 の 9日目の記事です。

昨日は@Carol_fanさんの『フロントエンドで自動ユニットテストについて』

という記事でした。

私も今、テストについて勉強をしているため、インプット&アウトプットをして

経験を重ねていきたいと思います!

さて、今回はFlutterでのアセット管理において、flutter_genという便利なパッケージが

ありますので、そのご紹介をしたいと思います!

自己紹介

TRIALの基盤チームにてモバイルアプリの開発に従事しています。

興味がある方は、以下をご参照下さい。

Retail AI:
https://www.retail-ai.jp/

TRIAL:
https://www.trial-net.co.jp/

採用ページ:

目次

アセットの追加

本記事の導入として、まずは一般的なアセットの追加方法について記載いたします。

手順は以下の通りです。

手順1

アセットを表示するために、まずはアセットを配置するディレクトリを

プロジェクトのルートディレクトリに作成します。

よくある命名はassetsかと思われますので、

今回はこのassetsディレクトリに様々なアセットが配置されることを想定して

assets/imagesといったようにimagesディレクトリを作成して、

このimagesディレクトリにあるpng形式の画像(check_mark.png)を

表示させようと思います。

画像はiconmonstrにあるものを利用させていただいています。

手順2

次にpubspec.yamlにて、flutterセクションのサブセクションにassetsを追加して、

その下にアセットのパスを追加します。

flutter:
  assets:
    - assets/images/check_mark.png

pub getも忘れずに。

これでアセットの準備は完了です。

手順3

このpng形式の画像を表示する簡単なサンプルを書いてみました。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Display PNG Image'),
        ),
        body: Center(
          child: Image.asset('assets/images/check_mark.png'),
        ),
      ),
    );
  }
}

結果はこちらです。

スクリーンショット 2024-11-16 10.26.52.png

ちゃんと表示されていますね。

問題点...

上記のコードのImage.asset('assets/images/check_mark.png')

とのようにパスを直接指定することでもアセットを参照することはできるのですが、

例えばこれを、assets/check_mark.pngassets/image/check_mark.png

といったようにTypoをした場合はアセットを参照できず、エラーになってしまいます...

スクリーンショット 2024-11-16 10.31.38.png

上記のようなパスを直接記述するやり方ですと

アセットを配置するディレクトリが変わったり、

そのファイルの名前が変わったりした場合、

対応するコードとpubspec.yamlの設定を修正しなければならず手間が掛かりますし、

また、コンパイルエラーも発生しないので、

実行するまでエラーに気付かない」、といった問題も起こります...

解決策🔍 ~ flutter_gen ~

そんな問題を解決してくれるパッケージがあります!

それがflutter_genです。

flutter_genはアセットを参照するコードを自動生成してくれるパッケージです。

flutter_genのセットアップ⚙️

flutter_genを利用するにはプロジェクトのルートディレクトリにて

以下のコマンドを使います

flutter pub add --dev build_runner flutter_gen_runner 

ソースコード生成ツールのbuild_runner

flutter_genのコードジェネレータであるflutter_gen_runnerを導入しています。

これで準備は完了です!

生成 & 利用🎉

実際に以下のコマンドを使ってコードを生成してみます。

flutter packages pub run build_runner build

すると、以下のようにディレクトリとファイルが生成されます。

スクリーンショット 2024-11-24 11.40.57.png

ちなにみ、生成された内容はこんな感じです。

/// GENERATED CODE - DO NOT MODIFY BY HAND
/// *****************************************************
///  FlutterGen
/// *****************************************************

// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use

import 'package:flutter/widgets.dart';

class $AssetsImagesGen {
  const $AssetsImagesGen();

  /// File path: assets/images/check_mark.png
  AssetGenImage get checkMark =>
      const AssetGenImage('assets/images/check_mark.png');

  /// List of all assets
  List<AssetGenImage> get values => [checkMark];
}

class Assets {
  Assets._();

  static const $AssetsImagesGen images = $AssetsImagesGen();
}

class AssetGenImage {
  const AssetGenImage(
    this._assetName, {
    this.size,
    this.flavors = const {},
  });

  final String _assetName;

  final Size? size;
  final Set<String> flavors;

  Image image({
    Key? key,
    AssetBundle? bundle,
    ImageFrameBuilder? frameBuilder,
    ImageErrorWidgetBuilder? errorBuilder,
    String? semanticLabel,
    bool excludeFromSemantics = false,
    double? scale,
    double? width,
    double? height,
    Color? color,
    Animation<double>? opacity,
    BlendMode? colorBlendMode,
    BoxFit? fit,
    AlignmentGeometry alignment = Alignment.center,
    ImageRepeat repeat = ImageRepeat.noRepeat,
    Rect? centerSlice,
    bool matchTextDirection = false,
    bool gaplessPlayback = true,
    bool isAntiAlias = false,
    String? package,
    FilterQuality filterQuality = FilterQuality.low,
    int? cacheWidth,
    int? cacheHeight,
  }) {
    return Image.asset(
      _assetName,
      key: key,
      bundle: bundle,
      frameBuilder: frameBuilder,
      errorBuilder: errorBuilder,
      semanticLabel: semanticLabel,
      excludeFromSemantics: excludeFromSemantics,
      scale: scale,
      width: width,
      height: height,
      color: color,
      opacity: opacity,
      colorBlendMode: colorBlendMode,
      fit: fit,
      alignment: alignment,
      repeat: repeat,
      centerSlice: centerSlice,
      matchTextDirection: matchTextDirection,
      gaplessPlayback: gaplessPlayback,
      isAntiAlias: isAntiAlias,
      package: package,
      filterQuality: filterQuality,
      cacheWidth: cacheWidth,
      cacheHeight: cacheHeight,
    );
  }

  ImageProvider provider({
    AssetBundle? bundle,
    String? package,
  }) {
    return AssetImage(
      _assetName,
      bundle: bundle,
      package: package,
    );
  }

  String get path => _assetName;

  String get keyName => _assetName;
}

早速、生成されたファイルを使ってコードを書き換えてみます。

import 'package:flutter/material.dart';

import 'gen/assets.gen.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Display PNG Image'),
        ),
        body: Center(
          // child: Image.asset('assets/images/check_mark.png'),
          child: Image.asset(Assets.images.checkMark.path),
        ),
      ),
    );
  }
}

画像アセットのパスを直接書くのではなく、

生成されたファイル内にあるAssetsクラスを介して

呼び出したい画像アセットのパスを呼び出しています。

結果はこちらです。

スクリーンショット 2024-11-24 12.08.39.png

ちゃんと表示されましたね🥳

アセットのパスやファイル名を変えたい場合は...?

使っているアセットのリソースファイルのパスを変えたり、

ファイル名を変えたい場合はどうするのか、これもやってみようと思います。

方法は同じなので、今回はパスを変えた場合の時だけやってみようと思います。

手順1 ~ pubspec.yamlのアセットのパス(またはファイル名)を修正

pubspec.yamlに記載したアセットのパスを修正します。

flutter:
  assets:
    - assets/check_mark.png

pub getも忘れずに!

手順2 ~ リソースファイルの配置(またはファイル名)を修正 ~

次に上記pubspec.yamlで指定した位置にアセットのリソースファイルを配置します。

ここまではflutter_genを使わない方法でも同じかと思います。

この時、pubspec.yamlで指定したアセットのパスやファイル名と、

実際のそれらが異なる場合は、手順3でコマンドが成功していても

コードがうまく生成されません。

スクリーンショット 2024-11-24 16.57.45.png

こんな内容がpubspec.yamlに表示されている場合は、

パスやファイル名が合っているか確認します。

修正後はpub getも忘れずに!

手順3 ~ コマンド実行~

先ほどのコード生成時に使ったコマンドと同じコマンドを使います。

flutter packages pub run build_runner build

この時、

[INFO] Building new asset graph completed, took 728ms
[INFO] Found 1 declared outputs which already exist on disk. This is likely because the`.dart_tool/build` folder was deleted, or you are submitting generated files to your source repository.
Delete these files?
1 - Delete
2 - Cancel build
3 - List conflicts

このような質問を返されると思いますので、

  • どのファイルでコンフリクトが起こっているかを確認したい場合は「3」

  • コンフリクトを起こしているファイルを消して再生成させたいなら「1」

  • そもそも、このコマンドを中止したいなら「2」

上記数字を選択します。

今回は直接「1」を選択しています。

すると、同名のファイルが新しく生成されます。

内容はこちらです。

/// GENERATED CODE - DO NOT MODIFY BY HAND
/// *****************************************************
///  FlutterGen
/// *****************************************************

// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use

import 'package:flutter/widgets.dart';

class Assets {
  Assets._();

  static const AssetGenImage checkMark = AssetGenImage('assets/check_mark.png');

  /// List of all assets
  static List<AssetGenImage> get values => [checkMark];
}

class AssetGenImage {
  const AssetGenImage(
    this._assetName, {
    this.size,
    this.flavors = const {},
  });

  final String _assetName;

  final Size? size;
  final Set<String> flavors;

  Image image({
    Key? key,
    AssetBundle? bundle,
    ImageFrameBuilder? frameBuilder,
    ImageErrorWidgetBuilder? errorBuilder,
    String? semanticLabel,
    bool excludeFromSemantics = false,
    double? scale,
    double? width,
    double? height,
    Color? color,
    Animation<double>? opacity,
    BlendMode? colorBlendMode,
    BoxFit? fit,
    AlignmentGeometry alignment = Alignment.center,
    ImageRepeat repeat = ImageRepeat.noRepeat,
    Rect? centerSlice,
    bool matchTextDirection = false,
    bool gaplessPlayback = true,
    bool isAntiAlias = false,
    String? package,
    FilterQuality filterQuality = FilterQuality.low,
    int? cacheWidth,
    int? cacheHeight,
  }) {
    return Image.asset(
      _assetName,
      key: key,
      bundle: bundle,
      frameBuilder: frameBuilder,
      errorBuilder: errorBuilder,
      semanticLabel: semanticLabel,
      excludeFromSemantics: excludeFromSemantics,
      scale: scale,
      width: width,
      height: height,
      color: color,
      opacity: opacity,
      colorBlendMode: colorBlendMode,
      fit: fit,
      alignment: alignment,
      repeat: repeat,
      centerSlice: centerSlice,
      matchTextDirection: matchTextDirection,
      gaplessPlayback: gaplessPlayback,
      isAntiAlias: isAntiAlias,
      package: package,
      filterQuality: filterQuality,
      cacheWidth: cacheWidth,
      cacheHeight: cacheHeight,
    );
  }

  ImageProvider provider({
    AssetBundle? bundle,
    String? package,
  }) {
    return AssetImage(
      _assetName,
      bundle: bundle,
      package: package,
    );
  }

  String get path => _assetName;

  String get keyName => _assetName;
}

一見すると違いに気付きにくいかもしれませんが、

Assetsクラスにあったimagesがなくなり、checkMarkを介してパスを取得していますね。

では、生成したコードを呼び出している部分を見てみましょう。

スクリーンショット 2024-11-24 16.35.41.png

このようにエラーが表示されているのが分かるかと思われます。

これがあることで、コードに問題があることに気付けますね。

実際に修正して実行してみましょう。

import 'package:flutter/material.dart';

import 'gen/assets.gen.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Display PNG Image'),
        ),
        body: Center(
          // child: Image.asset(Assets.images.checkMark.path),
          child: Image.asset(Assets.checkMark.path),
        ),
      ),
    );
  }
}

スクリーンショット 2024-11-24 12.08.39.png

ちゃんと表示されていますね🙌

まとめ

今回はflutter_genを使ったアセット管理についてでした。

このパッケージを使うことで型安全にアセットを管理でき、

認知上の負荷を下げることに繋がりそうですね。

次回は@satoshihiraishiさんの

『Quarkusで作ったアプリケーションをCloudRun関数で動かしてみる』

という記事です。

お楽しみに〜

参考

32
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?