Repository: FlutterGen/flutter_gen

Issue:
[FR]: Support flutter_svg asset transformed files (vector_graphics_compiler)
### Is there an existing issue for this?

- [x] I have searched the existing issues

### Describe the problem

`flutter_svg` allows us to precompile the SVGs so that the device doesn't have to deal with SVG parsing. This is using `vector_graphics_compiler` package. A few Flutter versions ago, [asset transformer](https://docs.flutter.dev/ui/assets/asset-transformation) support was also added. And `vector_graphics_compiler` can work as an asset transformer, which makes the compiled files be made as part of the Flutter app build process (also for flutter run, hot reload etc.) which is very convenient.

But there is one BUT, if we use precompiled vec files instead of normal text SVGs, we can't use `SvgPicture.asset`, but rather the `SvgPicture(AssetBytesLoader(path))`.

https://pub.dev/packages/flutter_svg#precompiling-and-optimizing-svgs

And `flutter_gen` only gives us the `svg()` method on SVG assets. We can of course make an extension method locally to give that ability, but in bigger codebases (or when we forget/make mistakes) we still have the `.svg()` available, which will result in a SVG not shown.

### Describe the solution

I propose to add a configuration for the flutter_gen in pubspec.yaml to tell the package whether to generate `.svg()` that uses `SvgPicture.asset` or the other one. Or even better, more convenient to the user (as we configure asset transformers per asset file/directory and not globally), will look in the pubspec's assets and generate this or that `SvgPicture` invocation depending on whether it has the `vector_graphics_compiler` transformer in the YAML for that particular file/directory.

For reference, even the [Asset transformation](https://docs.flutter.dev/ui/assets/asset-transformation#specifying-asset-transformations) Flutter docs use this vector compiler as the domain example of asset transformation :)

### Additional context

_No response_

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct


Discussion: See https://github.com/FlutterGen/flutter_gen/issues/514#issuecomment-2119101514.
Hey @AlexV525, I've taken a look and also looked in the flutter_gen code. The comment mentioned had the issue that's no longer an issue. In 4bf4cdc9e8f2ae74005ec1e7c4429fdba4b30969, the `pubspec.dart` JSON data class changed type of `assets` from `List<String>` to `List<Object>`, that you now use to fetch the flavors map specifically for one of flutter_gen features. The same can be done to get `transformers` field of the particular asset object to get transformers, and if a `vector_graphics_compiler` exists, pass this to `AssetType` and use this info in `svg_integration.dart` to decide whether to use the `SvgGenImage` or `SvgGenImage.vec` constructor in the generated file. It looks even more doable than I anticipated at first.

Please reopen, and if you don't have time for the development, I'd be happy to contribute, just tell me.

Cheers
Sure, I was occupied with business in recent months, so I was unable to produce contributions, but only doing triages and reviews.

The main idea (for me) was to make minimal changes to the core library and try to extend abilities by creating extensions. However, it was unclear how we should make extensions at the moment, so it's been postponed.

Fix patch:
diff --git a/README.md b/README.md
index 5c3e01d7..7a63385e 100644
--- a/README.md
+++ b/README.md
@@ -204,7 +204,10 @@ flutter:
     - assets/images/
     - assets/images/chip3/chip.jpg
     - assets/images/chip4/chip.jpg
-    - assets/images/icons/paint.svg
+    - path: assets/images/icons/paint.svg
+    - path: assets/images/icons/transformed.svg
+      transformers:
+        - package: vector_graphics_compiler
     - assets/images/icons/dart@test.svg
     - assets/json/fruits.json
     - assets/flare/Penguin.flr
@@ -380,7 +383,7 @@ flutter_gen:
     image: false
 ```
 
-If you are using SVG images with [flutter_svg](https://pub.dev/packages/flutter_svg) you can use the integration feature.
+If you are using SVG images with [flutter_svg](https://pub.dev/packages/flutter_svg) you can use the integration feature. This feature also supports using `vector_graphics_compiler` transformer and the produced code will use the `AssetBytesLoader` for such transformed assets.
 
 ```yaml
 # pubspec.yaml
@@ -391,6 +394,9 @@ flutter_gen:
 flutter:
   assets:
     - assets/images/icons/paint.svg
+    - path: assets/images/icons/transformed.svg
+      transformers:
+        - package: vector_graphics_compiler
 ```
 
 ```dart
diff --git a/packages/core/lib/generators/assets_generator.dart b/packages/core/lib/generators/assets_generator.dart
index 3b0a3222..32b077d8 100644
--- a/packages/core/lib/generators/assets_generator.dart
+++ b/packages/core/lib/generators/assets_generator.dart
@@ -176,24 +176,33 @@ List<FlavoredAsset> _getAssetRelativePathList(
 ) {
   // Normalize.
   final normalizedAssets = <Object>{...assets.whereType<String>()};
-  final normalizingMap = <String, Set<String>>{};
-  // Resolve flavored assets.
+  final normalizingMap =
+      <String, ({Set<String> flavors, Set<String> transformers})>{};
+  // Resolve flavored or transformed assets.
   for (final map in assets.whereType<YamlMap>()) {
     final path = (map['path'] as String).trim();
     final flavors =
         (map['flavors'] as YamlList?)?.toSet().cast<String>() ?? <String>{};
+    final transformers = (map['transformers'] as YamlList?)
+            ?.map(((t) => (t as YamlMap)['package'] as String))
+            .toSet() ??
+        {};
     if (normalizingMap.containsKey(path)) {
       // https://github.com/flutter/flutter/blob/5187cab7bdd434ca74abb45895d17e9fa553678a/packages/flutter_tools/lib/src/asset.dart#L1137-L1139
       throw StateError(
         'Multiple assets entries include the file "$path", '
-        'but they specify different lists of flavors.',
+        'but they specify different lists of flavors or transformers.',
       );
     }
-    normalizingMap[path] = flavors;
+    normalizingMap[path] = (flavors: flavors, transformers: transformers);
   }
   for (final entry in normalizingMap.entries) {
     normalizedAssets.add(
-      YamlMap.wrap({'path': entry.key, 'flavors': entry.value}),
+      YamlMap.wrap({
+        'path': entry.key,
+        'flavors': entry.value.flavors,
+        'transformers': entry.value.transformers,
+      }),
     );
   }
 
@@ -201,7 +210,11 @@ List<FlavoredAsset> _getAssetRelativePathList(
   for (final asset in normalizedAssets) {
     final FlavoredAsset tempAsset;
     if (asset is YamlMap) {
-      tempAsset = FlavoredAsset(path: asset['path'], flavors: asset['flavors']);
+      tempAsset = FlavoredAsset(
+        path: asset['path'],
+        flavors: asset['flavors'],
+        transformers: asset['transformers'],
+      );
     } else {
       tempAsset = FlavoredAsset(path: (asset as String).trim());
     }
@@ -238,14 +251,24 @@ AssetType _constructAssetTree(
 ) {
   // Relative path is the key
   final assetTypeMap = <String, AssetType>{
-    '.': AssetType(rootPath: rootPath, path: '.', flavors: {}),
+    '.': AssetType(
+      rootPath: rootPath,
+      path: '.',
+      flavors: {},
+      transformers: {},
+    ),
   };
   for (final asset in assetRelativePathList) {
     String path = asset.path;
     while (path != '.') {
       assetTypeMap.putIfAbsent(
         path,
-        () => AssetType(rootPath: rootPath, path: path, flavors: asset.flavors),
+        () => AssetType(
+          rootPath: rootPath,
+          path: path,
+          flavors: asset.flavors,
+          transformers: asset.transformers,
+        ),
       );
       path = dirname(path);
     }
@@ -462,6 +485,7 @@ Future<String> _flatStyleDefinition(
             rootPath: config.rootPath,
             path: assetPath.path,
             flavors: assetPath.flavors,
+            transformers: assetPath.transformers,
           ),
         )
         .mapToUniqueAssetType(style)
diff --git a/packages/core/lib/generators/integrations/svg_integration.dart b/packages/core/lib/generators/integrations/svg_integration.dart
index 568a48fd..778fec23 100644
--- a/packages/core/lib/generators/integrations/svg_integration.dart
+++ b/packages/core/lib/generators/integrations/svg_integration.dart
@@ -103,12 +103,15 @@ ${isPackage ? "\n  static const String package = '$packageName';" : ''}
   @override
   String get className => 'SvgGenImage';
 
+  static const vectorCompileTransformer = 'vector_graphics_compiler';
+
   @override
   String classInstantiate(AssetType asset) {
     // Query extra information about the SVG.
     final info = parseMetadata ? _getMetadata(asset) : null;
     final buffer = StringBuffer(className);
-    if (asset.extension == '.vec') {
+    if (asset.extension == '.vec' ||
+        asset.transformers.contains(vectorCompileTransformer)) {
       buffer.write('.vec');
     }
     buffer.write('(');
diff --git a/packages/core/lib/settings/asset_type.dart b/packages/core/lib/settings/asset_type.dart
index 872c96ba..8b2a666b 100644
--- a/packages/core/lib/settings/asset_type.dart
+++ b/packages/core/lib/settings/asset_type.dart
@@ -10,11 +10,13 @@ class AssetType {
     required this.rootPath,
     required this.path,
     required this.flavors,
+    required this.transformers,
   });
 
   final String rootPath;
   final String path;
   final Set<String> flavors;
+  final Set<String> transformers;
 
   final List<AssetType> _children = List.empty(growable: true);
   late final children = _children.sortedBy((e) => e.path);
@@ -55,7 +57,8 @@ class AssetType {
   String toString() => 'AssetType('
       'rootPath: $rootPath, '
       'path: $path, '
-      'flavors: $flavors'
+      'flavors: $flavors, '
+      'transformers: $transformers'
       ')';
 }
 
@@ -74,6 +77,7 @@ class UniqueAssetType extends AssetType {
           rootPath: assetType.rootPath,
           path: assetType.path,
           flavors: assetType.flavors,
+          transformers: assetType.transformers,
         );
 
   /// Convert the asset name to a correctly styled name, e.g camelCase or
@@ -113,7 +117,9 @@ class UniqueAssetType extends AssetType {
         'path: $path, '
         'style: $style, '
         'needExtension: $needExtension, '
-        'suffix: $suffix}';
+        'suffix: $suffix, '
+        'flavors: $flavors, '
+        'transformers: $transformers}';
   }
 }
 
diff --git a/packages/core/lib/settings/flavored_asset.dart b/packages/core/lib/settings/flavored_asset.dart
index 66e24aa0..c08905c7 100644
--- a/packages/core/lib/settings/flavored_asset.dart
+++ b/packages/core/lib/settings/flavored_asset.dart
@@ -2,18 +2,26 @@ class FlavoredAsset {
   const FlavoredAsset({
     required this.path,
     this.flavors = const {},
+    this.transformers = const {},
   });
 
   final String path;
   final Set<String> flavors;
+  final Set<String> transformers;
 
-  FlavoredAsset copyWith({String? path, Set<String>? flavors}) {
+  FlavoredAsset copyWith({
+    String? path,
+    Set<String>? flavors,
+    Set<String>? transformers,
+  }) {
     return FlavoredAsset(
       path: path ?? this.path,
       flavors: flavors ?? this.flavors,
+      transformers: transformers ?? this.transformers,
     );
   }
 
   @override
-  String toString() => 'FlavoredAsset(path: $path, flavors: $flavors)';
+  String toString() =>
+      'FlavoredAsset(path: $path, flavors: $flavors, transformers: $transformers)';
 }


Test patch:
diff --git a/packages/core/test/assets_gen_integrations_test.dart b/packages/core/test/assets_gen_integrations_test.dart
index a09d08f2..95957bf0 100644
--- a/packages/core/test/assets_gen_integrations_test.dart
+++ b/packages/core/test/assets_gen_integrations_test.dart
@@ -45,7 +45,12 @@ void main() {
     test('Integration.classInstantiate', () {
       expect(
         TestIntegration().classInstantiate(
-          AssetType(rootPath: resPath, path: 'assets/path', flavors: {'test'}),
+          AssetType(
+            rootPath: resPath,
+            path: 'assets/path',
+            flavors: {'test'},
+            transformers: {},
+          ),
         ),
         'TestIntegration(\'assets/path\', flavors: {\'test\'},)',
       );
@@ -63,6 +68,7 @@ void main() {
             rootPath: resPath,
             path: 'assets/path',
             flavors: {},
+            transformers: {},
           ),
         ),
         'SvgGenImage(\'assets/path\')',
@@ -73,26 +79,62 @@ void main() {
             rootPath: resPath,
             path: 'assets/path',
             flavors: {'test'},
+            transformers: {},
           ),
         ),
         'SvgGenImage(\'assets/path\', flavors: {\'test\'},)',
       );
+      expect(
+        integration.classInstantiate(
+          AssetType(
+            rootPath: resPath,
+            path: 'assets/path/dog.svg',
+            flavors: {},
+            transformers: {},
+          ),
+        ),
+        'SvgGenImage(\'assets/path/dog.svg\')',
+      );
       expect(
         integration.classInstantiate(
           AssetType(
             rootPath: resPath,
             path: 'assets/path/dog.vec',
             flavors: {},
+            transformers: {},
           ),
         ),
         'SvgGenImage.vec(\'assets/path/dog.vec\')',
       );
+      expect(
+        integration.classInstantiate(
+          AssetType(
+            rootPath: resPath,
+            path: 'assets/path/dog.svg',
+            flavors: {},
+            transformers: {'test'},
+          ),
+        ),
+        'SvgGenImage(\'assets/path/dog.svg\')',
+      );
+      expect(
+        integration.classInstantiate(
+          AssetType(
+            rootPath: resPath,
+            path: 'assets/path/dog.svg',
+            flavors: {},
+            transformers: {'vector_graphics_compiler'},
+          ),
+        ),
+        'SvgGenImage.vec(\'assets/path/dog.svg\')',
+      );
       expect(
         integration.isSupport(
           AssetType(
             rootPath: resPath,
             path: 'assets/path/dog.svg',
             flavors: {},
+            transformers: {},
           ),
         ),
         isTrue,
@@ -103,6 +145,18 @@ void main() {
             rootPath: resPath,
             path: 'assets/path/dog.vec',
             flavors: {},
+            transformers: {},
+          ),
+        ),
+        isTrue,
+      );
+      expect(
+        integration.isSupport(
+          AssetType(
+            rootPath: resPath,
+            path: 'assets/path/dog.svg',
+            flavors: {},
+            transformers: {'test'},
           ),
         ),
         isTrue,
@@ -113,6 +167,7 @@ void main() {
             rootPath: resPath,
             path: 'assets/path/dog.png',
             flavors: {},
+            transformers: {},
           ),
         ),
         isFalse,
@@ -147,6 +202,7 @@ void main() {
             rootPath: resPath,
             path: 'assets/path',
             flavors: {},
+            transformers: {},
           ),
         ),
         'RiveGenImage(\'assets/path\')',
@@ -157,6 +213,7 @@ void main() {
             rootPath: resPath,
             path: 'assets/path/dog.riv',
             flavors: {},
+            transformers: {},
           ),
         ),
         isTrue,
@@ -167,6 +224,7 @@ void main() {
             rootPath: resPath,
             path: 'assets/path/dog.json',
             flavors: {},
+            transformers: {},
           ),
         ),
         isFalse,
@@ -194,6 +252,7 @@ void main() {
             rootPath: resPath,
             path: 'assets/lottie',
             flavors: {},
+            transformers: {},
           ),
         ),
         'LottieGenImage(\'assets/lottie\')',
@@ -204,6 +263,7 @@ void main() {
             rootPath: resPath,
             path: 'assets/lottie/hamburger_arrow.json',
             flavors: {},
+            transformers: {},
           ),
         ),
         isTrue,
@@ -214,6 +274,7 @@ void main() {
             rootPath: resPath,
             path: 'assets/lottie/hamburger_arrow_without_version.json',
             flavors: {},
+            transformers: {},
           ),
         ),
         isFalse,
diff --git a/packages/core/test/assets_gen_test.dart b/packages/core/test/assets_gen_test.dart
index c15df926..a0120477 100644
--- a/packages/core/test/assets_gen_test.dart
+++ b/packages/core/test/assets_gen_test.dart
@@ -167,7 +167,14 @@ void main() {
 
       final List<AssetType> assets = tests.keys
           .sorted()
-          .map((e) => AssetType(rootPath: '', path: e, flavors: {}))
+          .map(
+            (e) => AssetType(
+              rootPath: '',
+              path: e,
+              flavors: {},
+              transformers: {},
+            ),
+          )
           .toList();
 
       final got = assets.mapToUniqueAssetType(camelCase);
diff --git a/packages/core/test/settings_test.dart b/packages/core/test/settings_test.dart
index 6a419132..b1baeb1a 100644
--- a/packages/core/test/settings_test.dart
+++ b/packages/core/test/settings_test.dart
@@ -11,6 +11,7 @@ void main() {
         rootPath: 'root',
         path: 'assets/single.jpg',
         flavors: {'flavor'},
+        transformers: {'transformer'},
       );
       expect(assetType, isA<AssetType>());
       expect(assetType.name, 'assets/single');
@@ -23,9 +24,16 @@ void main() {
           (e) => const SetEquality().equals(e.flavors, {'flavor'}),
         ),
       );
+      expect(
+        assetType,
+        predicate<AssetType>(
+          (e) => const SetEquality().equals(e.transformers, {'transformer'}),
+        ),
+      );
       expect(
         assetType.toString(),
-        'AssetType(rootPath: root, path: assets/single.jpg, flavors: {flavor})',
+        'AssetType(rootPath: root, path: assets/single.jpg, '
+        'flavors: {flavor}, transformers: {transformer})',
       );
     });
   });
@@ -34,7 +42,7 @@ void main() {
     test('constructor', () {
       expect(
         const FlavoredAsset(path: '').toString(),
-        'FlavoredAsset(path: , flavors: {})',
+        'FlavoredAsset(path: , flavors: {}, transformers: {})',
       );
       expect(
         const FlavoredAsset(path: 'assets/path'),
@@ -48,6 +56,10 @@ void main() {
         const FlavoredAsset(path: 'assets/path', flavors: {'test'}),
         isA<FlavoredAsset>(),
       );
+      expect(
+        const FlavoredAsset(path: 'assets/path', transformers: {'test'}),
+        isA<FlavoredAsset>(),
+      );
       expect(
         const FlavoredAsset(path: '1').copyWith(path: '2'),
         predicate<FlavoredAsset>((e) => e.path == '2'),
@@ -58,6 +70,12 @@ void main() {
           (e) => const SetEquality().equals(e.flavors, {'test'}),
         ),
       );
+      expect(
+        const FlavoredAsset(path: '1').copyWith(transformers: {'test'}),
+        predicate<FlavoredAsset>(
+          (e) => const SetEquality().equals(e.transformers, {'test'}),
+        ),
+      );
     });
   });
 
diff --git a/packages/core/test_resources/actual_data/assets_assets_svg_integrations.gen.dart b/packages/core/test_resources/actual_data/assets_assets_svg_integrations.gen.dart
index 6457b702..7f69e593 100644
--- a/packages/core/test_resources/actual_data/assets_assets_svg_integrations.gen.dart
+++ b/packages/core/test_resources/actual_data/assets_assets_svg_integrations.gen.dart
@@ -30,8 +30,11 @@ class $AssetsImagesIconsGen {
   SvgGenImage get fuchsia =>
       const SvgGenImage('assets/images/icons/fuchsia.svg');
 
+  /// File path: assets/images/icons/kmm.svg
+  SvgGenImage get kmm => const SvgGenImage.vec('assets/images/icons/kmm.svg');
+
   /// List of all assets
-  List<SvgGenImage> get values => [dartTest, fuchsia];
+  List<SvgGenImage> get values => [dartTest, fuchsia, kmm];
 }
 
 class Assets {
diff --git a/packages/core/test_resources/pubspec_assets_svg_integrations.yaml b/packages/core/test_resources/pubspec_assets_svg_integrations.yaml
index b43861e9..3ab9ea3e 100644
--- a/packages/core/test_resources/pubspec_assets_svg_integrations.yaml
+++ b/packages/core/test_resources/pubspec_assets_svg_integrations.yaml
@@ -11,3 +11,6 @@ flutter:
   assets:
     - assets/images/icons/dart@test.svg
     - assets/images/icons/fuchsia.svg
+    - path: assets/images/icons/kmm.svg
+      transformers:
+        - package: vector_graphics_compiler