Skip to content

Instantly share code, notes, and snippets.

@timmaffett
Last active May 20, 2024 23:22
Show Gist options
  • Save timmaffett/57b74391da32c8896f16e3b64043ed59 to your computer and use it in GitHub Desktop.
Save timmaffett/57b74391da32c8896f16e3b64043ed59 to your computer and use it in GitHub Desktop.
Flutter: AssetBundleFindHelpers extension class on AssetBundle
import 'package:flutter/services.dart';
/// Implements @matthew-carroll 's proposal in https://github.com/flutter/flutter/issues/137043
///
/// Example use/results
///
/// in main():
///
/// await rootBundle.initAssetBundleFindHelpers(); //strictly speaking this does not need to be awaited
///
///
/// somewhere in a build():
///
/// debugPrint('assets() list is ${DefaultAssetBundle.of(context).assets}');
/// debugPrint('Assets with PNG extension ${DefaultAssetBundle.of(context).findAssetsByExtension('png')}');
/// debugPrint('Assets with SVG extension ${DefaultAssetBundle.of(context).findAssetsByExtension('.svg')}');
/// debugPrint('Assets with "barcode" name ${DefaultAssetBundle.of(context).findAssetsByName('barcode')}');
/// debugPrint('Assets with "barcode.png" name ${DefaultAssetBundle.of(context).findAssetsByName('barcode.png')}');
/// debugPrint('Assets with "images/barcode" name ${DefaultAssetBundle.of(context).findAssetsByName('images/barcode')}');
/// debugPrint('Assets with "images/barcode.png" name ${DefaultAssetBundle.of(context).findAssetsByName('images/barcode.png')}');
/// debugPrint('Assets with "car_icon" name ${DefaultAssetBundle.of(context).findAssetsByName('car_icon')}');
/// debugPrint('Assets with "arrow-left" extension ${DefaultAssetBundle.of(context).findAssetsByName('arrow-left')}');
///
///
/// Console displays:
///
/// assets() list is [assets/barcode.svg,assets/images/ae.png,assets/images/barcode-big.png,assets/images/barcode.png,
/// assets/images/other-barcode.png,assets/images/calendar.png,assets/images/car_icon.png,assets/images/car_icon_on_white.png,
/// assets/images/card.png,assets/barcode.svg,assets/images/24-support.svg,assets/images/alarm.svg,assets/images/arrow-down.svg,
/// assets/images/arrow-left.svg]
/// Assets with PNG extension [assets/images/ae.png,assets/images/barcode-big.png,assets/images/barcode.png,assets/images/other-barcode.png
/// assets/images/calendar.png,assets/images/car_icon.png,assets/images/car_icon_on_white.png,assets/images/card.png]
/// Assets with SVG extension [assets/barcode.svg,assets/images/24-support.svg,assets/images/alarm.svg,
/// assets/images/arrow-down.svg,assets/images/arrow-left.svg]
/// Assets with "barcode" name [assets/barcode.svg, assets/images/barcode.png]
/// Assets with "barcode.png" name [assets/images/barcode.png]
/// Assets with "images/barcode" name [assets/images/barcode.png]
/// Assets with "images/barcode.png" name [assets/images/barcode.png]
/// Assets with "car_icon" name [assets/images/car_icon.png]
/// Assets with "arrow-left" extension [assets/images/arrow-left.svg]
extension AssetBundleFindHelpers on AssetBundle {
// Some cached info for extension (static maps because extensions can't add instance fields)
static Map<AssetBundle,AssetManifest> bundleToManifestMap = {};
static Map<AssetBundle,List<String>> bundleToAssetListMap = {};
static RegExp parseAssetNameWithExtensionRegEx = RegExp(r'^(.+\/)*(.+)\.(.+)$'); // group 1 is path, group 2 is filename, group 3 is extension
static RegExp parseAssetPathNameNoExtRegEx = RegExp(r'^(.+\/)*(.+)$'); // group 1 is path, group 2 is filename
/// Retrieve and cache the AssetManifest for the bundle.
/// It is required to call this before any other helper methods.
/// Typically this would be something like `await rootBundle.initAssetBundleFindHelpers();`
Future<AssetManifest> initAssetBundleFindHelpers() {
if(bundleToManifestMap.containsKey(this)) {
return Future.value(bundleToManifestMap[this]!);
}
final retAM = AssetManifest.loadFromAssetBundle(this);
retAM.then((am) {
// store AssetManifest in our cache map
bundleToManifestMap[this] = am;
});
return retAM;
}
/// Return list of assets for which the filename matches the specified [regex].
List<String> findAssetsByNameThatMatch(RegExp regex) {
if(!bundleToManifestMap.containsKey(this)) {
throw ('Call initAssetBundleFindHelpers() for this AssetBundle before calling this api extension.');
}
return assets.where((item) => regex.hasMatch(item)).toList();
}
/// Return list of assets for which match the specified [name].
/// The assets whole filename must match the specified [name].
/// Optionally a required prefix path can be added to [name].
/// Optionally a required extension can be added to [name].
List<String> findAssetsByName(String name) {
if(!bundleToManifestMap.containsKey(this)) {
throw ('Call initAssetBundleFindHelpers() for this AssetBundle before calling this api extension.');
}
String? targetPath, targetExt;
String targetName = name;
var match = parseAssetNameWithExtensionRegEx.firstMatch(name);
if(match!=null) {
// name has extension (and possibly path)
targetPath = match.group(1);
targetName = match.group(2) ?? name;
targetExt = match.group(3);
} else {
// name has no extension (and possibly path)
match = parseAssetPathNameNoExtRegEx.firstMatch(name);
if(match!=null) {
targetPath = match.group(1);
targetName = match.group(2) ?? name;
}
}
// escape any regex chars in name
final escapedName = RegExp.escape(targetName);
// make regexp to match this name with any prefix path or extension
// (name can contain subpath)
final regExForThisName = RegExp(r'^(.+\/)*' + escapedName + r'\.(.+)');
final prelimMatches = findAssetsByNameThatMatch(regExForThisName);
// now prune down prelim match list as required by possible path or extension on incoming name
return prelimMatches.where((item) {
final match = parseAssetNameWithExtensionRegEx.firstMatch(item);
return match!=null &&
match.group(2)==targetName &&
(targetPath==null || (match.group(1)!=null && match.group(1)!.endsWith(targetPath))) &&
(targetExt==null || (match.group(3)!=null && match.group(3)==targetExt));
}).toList();
}
/// Return list of all assets with specified [extension].
/// The [extension] can optionally included the leading '.', ie. 'png' or '.png'.
List<String> findAssetsByExtension(String extension) {
if(!bundleToManifestMap.containsKey(this)) {
throw ('Call initAssetBundleFindHelpers() for this AssetBundle before calling this api extension.');
}
if(!extension.startsWith('.')) extension = '.$extension';
return assets.where((item) => item.endsWith(extension)).toList();
}
// Return a list of all assets in the manifest.
List<String> get assets {
if(!bundleToManifestMap.containsKey(this)) {
throw ('Call initAssetBundleFindHelpers() for this AssetBundle before calling this api extension.');
}
// cache the asset list so we don't keep recreating it
if(bundleToAssetListMap.containsKey(this)) {
return bundleToAssetListMap[this]!;
}
return (bundleToAssetListMap[this] = bundleToManifestMap[this]!.listAssets());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment