Skip to content

Commit 4bf4cdc

Browse files
authored
✨ Flavored assets (#530)
## What does this change? Resolves #494 🎯 Generated assets would contain the flavor info specified in the `pubspec.yaml`, but only as a constant not involved with actual flavoring. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] This change requires a documentation update
1 parent f678d54 commit 4bf4cdc

36 files changed

+1257
-229
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,18 @@ flutter:
167167
168168
These configurations will generate **`assets.gen.dart`** under the **`lib/gen/`** directory by default.
169169

170+
#### Flavored assets
171+
172+
Flutter supports
173+
[Conditionally bundling assets based on flavor](https://docs.flutter.dev/deployment/flavors#conditionally-bundling-assets-based-on-flavor).
174+
Assets are only available with flavors if specified.
175+
`flutter_gen` will generate the specified `flavors` for assets regardless the current flavor.
176+
The `flavors` field accessible though `.flavors`, for example:
177+
178+
```dart
179+
print(MyAssets.images.chip4.flavors); // -> {'extern'}
180+
```
181+
170182
#### Excluding generating for assets
171183

172184
You can specify `flutter_gen > assets > exclude` using `Glob` patterns to exclude particular assets.

examples/example/lib/gen/assets.gen.dart

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ class $AssetsImagesGen {
3535
/// File path: assets/images/chip2.jpg
3636
AssetGenImage get chip2 => const AssetGenImage('assets/images/chip2.jpg');
3737

38+
/// Directory path: assets/images/chip3
39+
$AssetsImagesChip3Gen get chip3 => const $AssetsImagesChip3Gen();
40+
3841
/// Directory path: assets/images/chip4
3942
$AssetsImagesChip4Gen get chip4 => const $AssetsImagesChip4Gen();
4043

@@ -142,12 +145,25 @@ class $AssetsUnknownGen {
142145
List<String> get values => [changelog, readme, unknownMimeType];
143146
}
144147

148+
class $AssetsImagesChip3Gen {
149+
const $AssetsImagesChip3Gen();
150+
151+
/// File path: assets/images/chip3/chip3.jpg
152+
AssetGenImage get chip3 =>
153+
const AssetGenImage('assets/images/chip3/chip3.jpg');
154+
155+
/// List of all assets
156+
List<AssetGenImage> get values => [chip3];
157+
}
158+
145159
class $AssetsImagesChip4Gen {
146160
const $AssetsImagesChip4Gen();
147161

148162
/// File path: assets/images/chip4/chip4.jpg
149-
AssetGenImage get chip4 =>
150-
const AssetGenImage('assets/images/chip4/chip4.jpg');
163+
AssetGenImage get chip4 => const AssetGenImage(
164+
'assets/images/chip4/chip4.jpg',
165+
flavors: {'extern'},
166+
);
151167

152168
/// List of all assets
153169
List<AssetGenImage> get values => [chip4];
@@ -202,11 +218,16 @@ class MyAssets {
202218
}
203219

204220
class AssetGenImage {
205-
const AssetGenImage(this._assetName, {this.size = null});
221+
const AssetGenImage(
222+
this._assetName, {
223+
this.size,
224+
this.flavors = const {},
225+
});
206226

207227
final String _assetName;
208228

209229
final Size? size;
230+
final Set<String> flavors;
210231

211232
Image image({
212233
Key? key,
@@ -280,17 +301,19 @@ class AssetGenImage {
280301
class SvgGenImage {
281302
const SvgGenImage(
282303
this._assetName, {
283-
this.size = null,
304+
this.size,
305+
this.flavors = const {},
284306
}) : _isVecFormat = false;
285307

286308
const SvgGenImage.vec(
287309
this._assetName, {
288-
this.size = null,
310+
this.size,
311+
this.flavors = const {},
289312
}) : _isVecFormat = true;
290313

291314
final String _assetName;
292-
293315
final Size? size;
316+
final Set<String> flavors;
294317
final bool _isVecFormat;
295318

296319
SvgPicture svg({
@@ -313,12 +336,23 @@ class SvgGenImage {
313336
@deprecated BlendMode colorBlendMode = BlendMode.srcIn,
314337
@deprecated bool cacheColorFilter = false,
315338
}) {
339+
final BytesLoader loader;
340+
if (_isVecFormat) {
341+
loader = AssetBytesLoader(
342+
_assetName,
343+
assetBundle: bundle,
344+
packageName: package,
345+
);
346+
} else {
347+
loader = SvgAssetLoader(
348+
_assetName,
349+
assetBundle: bundle,
350+
packageName: package,
351+
theme: theme,
352+
);
353+
}
316354
return SvgPicture(
317-
_isVecFormat
318-
? AssetBytesLoader(_assetName,
319-
assetBundle: bundle, packageName: package)
320-
: SvgAssetLoader(_assetName,
321-
assetBundle: bundle, packageName: package, theme: theme),
355+
loader,
322356
key: key,
323357
matchTextDirection: matchTextDirection,
324358
width: width,
@@ -342,9 +376,13 @@ class SvgGenImage {
342376
}
343377

344378
class FlareGenImage {
345-
const FlareGenImage(this._assetName);
379+
const FlareGenImage(
380+
this._assetName, {
381+
this.flavors = const {},
382+
});
346383

347384
final String _assetName;
385+
final Set<String> flavors;
348386

349387
FlareActor flare({
350388
String? boundsNode,
@@ -385,9 +423,13 @@ class FlareGenImage {
385423
}
386424

387425
class RiveGenImage {
388-
const RiveGenImage(this._assetName);
426+
const RiveGenImage(
427+
this._assetName, {
428+
this.flavors = const {},
429+
});
389430

390431
final String _assetName;
432+
final Set<String> flavors;
391433

392434
RiveAnimation rive({
393435
String? artboard,
@@ -422,9 +464,13 @@ class RiveGenImage {
422464
}
423465

424466
class LottieGenImage {
425-
const LottieGenImage(this._assetName);
467+
const LottieGenImage(
468+
this._assetName, {
469+
this.flavors = const {},
470+
});
426471

427472
final String _assetName;
473+
final Set<String> flavors;
428474

429475
LottieBuilder lottie({
430476
Animation<double>? controller,

examples/example/pubspec.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ flutter_gen:
5151
style: dot-delimiter
5252

5353
exclude:
54-
- assets/images/chip3/chip3.jpg
5554
- assets-extern/*
5655
- pictures/chip5.jpg
5756

@@ -76,7 +75,6 @@ flutter:
7675
- assets/images/
7776
- assets/images/chip3/chip3.jpg
7877
- assets/images/chip3/chip3.jpg # duplicated
79-
- assets/images/chip4/
8078
- assets/images/icons/fuchsia.svg
8179
- assets/images/icons/kmm.svg
8280
- assets/images/icons/paint.svg
@@ -91,6 +89,10 @@ flutter:
9189
- assets/mix/
9290
- assets-extern/
9391
- pictures/chip5.jpg
92+
93+
- path: assets/images/chip4/chip4.jpg
94+
flavors:
95+
- extern
9496
fonts:
9597
- family: Raleway
9698
fonts:

packages/core/lib/generators/assets_generator.dart

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ import 'package:flutter_gen_core/generators/integrations/rive_integration.dart';
1313
import 'package:flutter_gen_core/generators/integrations/svg_integration.dart';
1414
import 'package:flutter_gen_core/settings/asset_type.dart';
1515
import 'package:flutter_gen_core/settings/config.dart';
16+
import 'package:flutter_gen_core/settings/flavored_asset.dart';
1617
import 'package:flutter_gen_core/settings/pubspec.dart';
1718
import 'package:flutter_gen_core/utils/error.dart';
1819
import 'package:flutter_gen_core/utils/string.dart';
1920
import 'package:glob/glob.dart';
2021
import 'package:path/path.dart';
22+
import 'package:yaml/yaml.dart';
2123

2224
class AssetsGenConfig {
2325
AssetsGenConfig._(
@@ -41,7 +43,7 @@ class AssetsGenConfig {
4143
final String rootPath;
4244
final String _packageName;
4345
final FlutterGen flutterGen;
44-
final List<String> assets;
46+
final List<Object> assets;
4547
final List<Glob> exclude;
4648

4749
String get packageParameterLiteral =>
@@ -194,51 +196,89 @@ String? generatePackageNameForConfig(AssetsGenConfig config) {
194196
}
195197
}
196198

197-
/// Returns a list of all releative path assets that are to be considered.
198-
List<String> _getAssetRelativePathList(
199+
/// Returns a list of all relative path assets that are to be considered.
200+
List<FlavoredAsset> _getAssetRelativePathList(
199201
/// The absolute root path of the assets directory.
200202
String rootPath,
201203

202-
/// List of assets as provided the `flutter`.`assets` section in the pubspec.yaml.
203-
List<String> assets,
204+
/// List of assets as provided the `flutter -> assets`
205+
/// section in the pubspec.yaml.
206+
List<Object> assets,
204207

205-
/// List of globs as provided the `flutter_gen`.`assets`.`exclude` section in the pubspec.yaml.
208+
/// List of globs as provided the `flutter_gen -> assets -> exclude`
209+
/// section in the pubspec.yaml.
206210
List<Glob> excludes,
207211
) {
208-
final assetRelativePathList = <String>[];
209-
for (final assetName in assets) {
210-
final assetAbsolutePath = join(rootPath, assetName);
212+
// Normalize.
213+
final normalizedAssets = <Object>{...assets.whereType<String>()};
214+
final normalizingMap = <String, Set<String>>{};
215+
// Resolve flavored assets.
216+
for (final map in assets.whereType<YamlMap>()) {
217+
final path = (map['path'] as String).trim();
218+
final flavors =
219+
(map['flavors'] as YamlList?)?.toSet().cast<String>() ?? <String>{};
220+
if (normalizingMap.containsKey(path)) {
221+
// https://github.com/flutter/flutter/blob/5187cab7bdd434ca74abb45895d17e9fa553678a/packages/flutter_tools/lib/src/asset.dart#L1137-L1139
222+
throw StateError(
223+
'Multiple assets entries include the file "$path", '
224+
'but they specify different lists of flavors.',
225+
);
226+
}
227+
normalizingMap[path] = flavors;
228+
}
229+
for (final entry in normalizingMap.entries) {
230+
normalizedAssets.add(
231+
YamlMap.wrap({'path': entry.key, 'flavors': entry.value}),
232+
);
233+
}
234+
235+
final assetRelativePathList = <FlavoredAsset>[];
236+
for (final asset in normalizedAssets) {
237+
final FlavoredAsset tempAsset;
238+
if (asset is YamlMap) {
239+
tempAsset = FlavoredAsset(path: asset['path'], flavors: asset['flavors']);
240+
} else {
241+
tempAsset = FlavoredAsset(path: (asset as String).trim());
242+
}
243+
final assetAbsolutePath = join(rootPath, tempAsset.path);
211244
if (FileSystemEntity.isDirectorySync(assetAbsolutePath)) {
212245
assetRelativePathList.addAll(Directory(assetAbsolutePath)
213246
.listSync()
214247
.whereType<File>()
215-
.map((e) => relative(e.path, from: rootPath))
248+
.map(
249+
(e) => tempAsset.copyWith(path: relative(e.path, from: rootPath)),
250+
)
216251
.toList());
217252
} else if (FileSystemEntity.isFileSync(assetAbsolutePath)) {
218-
assetRelativePathList.add(relative(assetAbsolutePath, from: rootPath));
253+
assetRelativePathList.add(
254+
tempAsset.copyWith(path: relative(assetAbsolutePath, from: rootPath)),
255+
);
219256
}
220257
}
221258

222259
if (excludes.isEmpty) {
223260
return assetRelativePathList;
224261
}
225-
226262
return assetRelativePathList
227-
.where((file) => !excludes.any((exclude) => exclude.matches(file)))
263+
.where((asset) => !excludes.any((exclude) => exclude.matches(asset.path)))
228264
.toList();
229265
}
230266

231267
AssetType _constructAssetTree(
232-
List<String> assetRelativePathList, String rootPath) {
268+
List<FlavoredAsset> assetRelativePathList,
269+
String rootPath,
270+
) {
233271
// Relative path is the key
234272
final assetTypeMap = <String, AssetType>{
235-
'.': AssetType(rootPath: rootPath, path: '.'),
273+
'.': AssetType(rootPath: rootPath, path: '.', flavors: {}),
236274
};
237-
for (final assetPath in assetRelativePathList) {
238-
var path = assetPath;
275+
for (final asset in assetRelativePathList) {
276+
String path = asset.path;
239277
while (path != '.') {
240278
assetTypeMap.putIfAbsent(
241-
path, () => AssetType(rootPath: rootPath, path: path));
279+
path,
280+
() => AssetType(rootPath: rootPath, path: path, flavors: asset.flavors),
281+
);
242282
path = dirname(path);
243283
}
244284
}
@@ -320,7 +360,8 @@ String _dotDelimiterStyleDefinition(
320360
final assetsStaticStatements = <_Statement>[];
321361

322362
final assetTypeQueue = ListQueue<AssetType>.from(
323-
_constructAssetTree(assetRelativePathList, rootPath).children);
363+
_constructAssetTree(assetRelativePathList, rootPath).children,
364+
);
324365

325366
while (assetTypeQueue.isNotEmpty) {
326367
final assetType = assetTypeQueue.removeFirst();
@@ -428,14 +469,20 @@ String _flatStyleDefinition(
428469
List<Integration> integrations,
429470
String Function(String) style,
430471
) {
431-
final statements = _getAssetRelativePathList(
472+
final List<FlavoredAsset> paths = _getAssetRelativePathList(
432473
config.rootPath,
433474
config.assets,
434475
config.exclude,
435-
)
436-
.distinct()
437-
.sorted()
438-
.map((assetPath) => AssetType(rootPath: config.rootPath, path: assetPath))
476+
);
477+
paths.sort(((a, b) => a.path.compareTo(b.path)));
478+
final statements = paths
479+
.map(
480+
(assetPath) => AssetType(
481+
rootPath: config.rootPath,
482+
path: assetPath.path,
483+
flavors: assetPath.flavors,
484+
),
485+
)
439486
.mapToUniqueAssetType(style)
440487
.map(
441488
(e) => _createAssetTypeStatement(

packages/core/lib/generators/integrations/flare_integration.dart

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@ class FlareIntegration extends Integration {
1717
String get classOutput => _classDefinition;
1818

1919
String get _classDefinition => '''class FlareGenImage {
20-
const FlareGenImage(this._assetName);
20+
const FlareGenImage(
21+
this._assetName, {
22+
this.flavors = const {},
23+
});
2124
2225
final String _assetName;
26+
final Set<String> flavors;
27+
2328
${isPackage ? "\n static const String package = '$packageName';" : ''}
2429
2530
FlareActor flare({
@@ -63,10 +68,6 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''}
6368
@override
6469
String get className => 'FlareGenImage';
6570

66-
@override
67-
String classInstantiate(AssetType asset) =>
68-
'FlareGenImage(\'${asset.posixStylePath}\')';
69-
7071
@override
7172
bool isSupport(AssetType asset) => asset.extension == '.flr';
7273

0 commit comments

Comments
 (0)