blob: c948067e4cb3910bb26a397c049ebdcc501b4c22 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'asset_bundle.dart';
import 'message_codecs.dart';
// We use .bin as the extension since it is well-known to represent
// data in some arbitrary binary format.
const String _kAssetManifestFilename = 'AssetManifest.bin';
// We use the same bin file for the web, but re-encoded as JSON(base64(bytes))
// so it can be downloaded by even the dumbest of browsers.
// See https://github.com/flutter/flutter/issues/128456
const String _kAssetManifestWebFilename = 'AssetManifest.bin.json';
/// Contains details about available assets and their variants.
/// See [Resolution-aware image assets](https://docs.flutter.dev/ui/assets-and-images#resolution-aware)
/// to learn about asset variants and how to declare them.
abstract class AssetManifest {
/// Loads asset manifest data from an [AssetBundle] object and creates an
/// [AssetManifest] object from that data.
static Future<AssetManifest> loadFromAssetBundle(AssetBundle bundle) {
// The AssetManifest file contains binary data.
//
// On the web, the build process wraps this binary data in json+base64 so
// it can be transmitted over the network without special configuration
// (see #131382).
if (kIsWeb) {
// On the web, the AssetManifest is downloaded as a String, then
// json+base64-decoded to get to the binary data.
return bundle.loadStructuredData(_kAssetManifestWebFilename, (String jsonData) async {
// Decode the manifest JSON file to the underlying BIN, and convert to ByteData.
final ByteData message = ByteData.sublistView(base64.decode(json.decode(jsonData) as String));
// Now we can keep operating as usual.
return _AssetManifestBin.fromStandardMessageCodecMessage(message);
});
}
// On every other platform, the binary file contents are used directly.
return bundle.loadStructuredBinaryData(_kAssetManifestFilename, _AssetManifestBin.fromStandardMessageCodecMessage);
}
/// Lists the keys of all main assets. This does not include assets
/// that are variants of other assets.
///
/// The logical key maps to the path of an asset specified in the pubspec.yaml
/// file at build time.
///
/// See [Specifying assets](https://docs.flutter.dev/development/ui/assets-and-images#specifying-assets)
/// and [Loading assets](https://docs.flutter.dev/development/ui/assets-and-images#loading-assets)
/// for more information.
List<String> listAssets();
/// Retrieves metadata about an asset and its variants. Returns null if the
/// key was not found in the asset manifest.
///
/// This method considers a main asset to be a variant of itself. The returned
/// list will include it if it exists.
List<AssetMetadata>? getAssetVariants(String key);
}
// Lazily parses the binary asset manifest into a data structure that's easier to work
// with.
//
// The binary asset manifest is a map of asset keys to a list of objects
// representing the asset's variants.
//
// The entries with each variant object are:
// - "asset": the location of this variant to load it from.
// - "dpr": The device-pixel-ratio that the asset is best-suited for.
//
// New fields could be added to this object schema to support new asset variation
// features, such as themes, locale/region support, reading directions, and so on.
class _AssetManifestBin implements AssetManifest {
_AssetManifestBin(Map<Object?, Object?> standardMessageData): _data = standardMessageData;
factory _AssetManifestBin.fromStandardMessageCodecMessage(ByteData message) {
final dynamic data = const StandardMessageCodec().decodeMessage(message);
return _AssetManifestBin(data as Map<Object?, Object?>);
}
final Map<Object?, Object?> _data;
final Map<String, List<AssetMetadata>> _typeCastedData = <String, List<AssetMetadata>>{};
@override
List<AssetMetadata>? getAssetVariants(String key) {
// We lazily delay typecasting to prevent a performance hiccup when parsing
// large asset manifests. This is important to keep an app's first asset
// load fast.
if (!_typeCastedData.containsKey(key)) {
final Object? variantData = _data[key];
if (variantData == null) {
return null;
}
_typeCastedData[key] = ((_data[key] ?? <Object?>[]) as Iterable<Object?>)
.cast<Map<Object?, Object?>>()
.map((Map<Object?, Object?> data) {
final String asset = data['asset']! as String;
final Object? dpr = data['dpr'];
return AssetMetadata(
key: data['asset']! as String,
targetDevicePixelRatio: dpr as double?,
main: key == asset,
);
})
.toList();
_data.remove(key);
}
return _typeCastedData[key]!;
}
@override
List<String> listAssets() {
return <String>[..._data.keys.cast<String>(), ..._typeCastedData.keys];
}
}
/// Contains information about an asset.
@immutable
class AssetMetadata {
/// Creates an object containing information about an asset.
const AssetMetadata({
required this.key,
required this.targetDevicePixelRatio,
required this.main,
});
/// The device pixel ratio that this asset is most ideal for. This is determined
/// by the name of the parent folder of the asset file. For example, if the
/// parent folder is named "3.0x", the target device pixel ratio of that
/// asset will be interpreted as 3.
///
/// This will be null if the parent folder name is not a ratio value followed
/// by an "x".
///
/// See [Resolution-aware image assets](https://docs.flutter.dev/development/ui/assets-and-images#resolution-aware)
/// for more information.
final double? targetDevicePixelRatio;
/// The asset's key, which is the path to the asset specified in the pubspec.yaml
/// file at build time.
final String key;
/// Whether or not this is a main asset. In other words, this is true if
/// this asset is not a variant of another asset.
final bool main;
}