| // 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:async'; |
| import 'dart:convert'; |
| import 'dart:io'; |
| import 'dart:typed_data'; |
| |
| import 'package:flutter/foundation.dart'; |
| |
| import 'binary_messenger.dart'; |
| |
| /// A collection of resources used by the application. |
| /// |
| /// Asset bundles contain resources, such as images and strings, that can be |
| /// used by an application. Access to these resources is asynchronous so that |
| /// they can be transparently loaded over a network (e.g., from a |
| /// [NetworkAssetBundle]) or from the local file system without blocking the |
| /// application's user interface. |
| /// |
| /// Applications have a [rootBundle], which contains the resources that were |
| /// packaged with the application when it was built. To add resources to the |
| /// [rootBundle] for your application, add them to the `assets` subsection of |
| /// the `flutter` section of your application's `pubspec.yaml` manifest. |
| /// |
| /// For example: |
| /// |
| /// ```yaml |
| /// name: my_awesome_application |
| /// flutter: |
| /// assets: |
| /// - images/hamilton.jpeg |
| /// - images/lafayette.jpeg |
| /// ``` |
| /// |
| /// Rather than accessing the [rootBundle] global static directly, consider |
| /// obtaining the [AssetBundle] for the current [BuildContext] using |
| /// [DefaultAssetBundle.of]. This layer of indirection lets ancestor widgets |
| /// substitute a different [AssetBundle] (e.g., for testing or localization) at |
| /// runtime rather than directly replying upon the [rootBundle] created at build |
| /// time. For convenience, the [WidgetsApp] or [MaterialApp] widget at the top |
| /// of the widget hierarchy configures the [DefaultAssetBundle] to be the |
| /// [rootBundle]. |
| /// |
| /// See also: |
| /// |
| /// * [DefaultAssetBundle] |
| /// * [NetworkAssetBundle] |
| /// * [rootBundle] |
| abstract class AssetBundle { |
| /// Retrieve a binary resource from the asset bundle as a data stream. |
| /// |
| /// Throws an exception if the asset is not found. |
| Future<ByteData> load(String key); |
| |
| /// Retrieve a string from the asset bundle. |
| /// |
| /// Throws an exception if the asset is not found. |
| /// |
| /// If the `cache` argument is set to false, then the data will not be |
| /// cached, and reading the data may bypass the cache. This is useful if the |
| /// caller is going to be doing its own caching. (It might not be cached if |
| /// it's set to true either, that depends on the asset bundle |
| /// implementation.) |
| Future<String> loadString(String key, { bool cache = true }) async { |
| final ByteData data = await load(key); |
| if (data == null) |
| throw FlutterError('Unable to load asset: $key'); |
| if (data.lengthInBytes < 10 * 1024) { |
| // 10KB takes about 3ms to parse on a Pixel 2 XL. |
| // See: https://github.com/dart-lang/sdk/issues/31954 |
| return utf8.decode(data.buffer.asUint8List()); |
| } |
| return compute(_utf8decode, data, debugLabel: 'UTF8 decode for "$key"'); |
| } |
| |
| static String _utf8decode(ByteData data) { |
| return utf8.decode(data.buffer.asUint8List()); |
| } |
| |
| /// Retrieve a string from the asset bundle, parse it with the given function, |
| /// and return the function's result. |
| /// |
| /// Implementations may cache the result, so a particular key should only be |
| /// used with one parser for the lifetime of the asset bundle. |
| Future<T> loadStructuredData<T>(String key, Future<T> parser(String value)); |
| |
| /// If this is a caching asset bundle, and the given key describes a cached |
| /// asset, then evict the asset from the cache so that the next time it is |
| /// loaded, the cache will be reread from the asset bundle. |
| void evict(String key) { } |
| |
| @override |
| String toString() => '${describeIdentity(this)}()'; |
| } |
| |
| /// An [AssetBundle] that loads resources over the network. |
| /// |
| /// This asset bundle does not cache any resources, though the underlying |
| /// network stack may implement some level of caching itself. |
| class NetworkAssetBundle extends AssetBundle { |
| /// Creates an network asset bundle that resolves asset keys as URLs relative |
| /// to the given base URL. |
| NetworkAssetBundle(Uri baseUrl) |
| : _baseUrl = baseUrl, |
| _httpClient = HttpClient(); |
| |
| final Uri _baseUrl; |
| final HttpClient _httpClient; |
| |
| Uri _urlFromKey(String key) => _baseUrl.resolve(key); |
| |
| @override |
| Future<ByteData> load(String key) async { |
| final HttpClientRequest request = await _httpClient.getUrl(_urlFromKey(key)); |
| final HttpClientResponse response = await request.close(); |
| if (response.statusCode != HttpStatus.ok) |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('Unable to load asset: $key'), |
| IntProperty('HTTP status code', response.statusCode), |
| ]); |
| final Uint8List bytes = await consolidateHttpClientResponseBytes(response); |
| return bytes.buffer.asByteData(); |
| } |
| |
| /// Retrieve a string from the asset bundle, parse it with the given function, |
| /// and return the function's result. |
| /// |
| /// The result is not cached. The parser is run each time the resource is |
| /// fetched. |
| @override |
| Future<T> loadStructuredData<T>(String key, Future<T> parser(String value)) async { |
| assert(key != null); |
| assert(parser != null); |
| return parser(await loadString(key)); |
| } |
| |
| // TODO(ianh): Once the underlying network logic learns about caching, we |
| // should implement evict(). |
| |
| @override |
| String toString() => '${describeIdentity(this)}($_baseUrl)'; |
| } |
| |
| /// An [AssetBundle] that permanently caches string and structured resources |
| /// that have been fetched. |
| /// |
| /// Strings (for [loadString] and [loadStructuredData]) are decoded as UTF-8. |
| /// Data that is cached is cached for the lifetime of the asset bundle |
| /// (typically the lifetime of the application). |
| /// |
| /// Binary resources (from [load]) are not cached. |
| abstract class CachingAssetBundle extends AssetBundle { |
| // TODO(ianh): Replace this with an intelligent cache, see https://github.com/flutter/flutter/issues/3568 |
| final Map<String, Future<String>> _stringCache = <String, Future<String>>{}; |
| final Map<String, Future<dynamic>> _structuredDataCache = <String, Future<dynamic>>{}; |
| |
| @override |
| Future<String> loadString(String key, { bool cache = true }) { |
| if (cache) |
| return _stringCache.putIfAbsent(key, () => super.loadString(key)); |
| return super.loadString(key); |
| } |
| |
| /// Retrieve a string from the asset bundle, parse it with the given function, |
| /// and return the function's result. |
| /// |
| /// The result of parsing the string is cached (the string itself is not, |
| /// unless you also fetch it with [loadString]). For any given `key`, the |
| /// `parser` is only run the first time. |
| /// |
| /// Once the value has been parsed, the future returned by this function for |
| /// subsequent calls will be a [SynchronousFuture], which resolves its |
| /// callback synchronously. |
| @override |
| Future<T> loadStructuredData<T>(String key, Future<T> parser(String value)) { |
| assert(key != null); |
| assert(parser != null); |
| if (_structuredDataCache.containsKey(key)) |
| return _structuredDataCache[key] as Future<T>; |
| Completer<T> completer; |
| Future<T> result; |
| loadString(key, cache: false).then<T>(parser).then<void>((T value) { |
| result = SynchronousFuture<T>(value); |
| _structuredDataCache[key] = result; |
| if (completer != null) { |
| // We already returned from the loadStructuredData function, which means |
| // we are in the asynchronous mode. Pass the value to the completer. The |
| // completer's future is what we returned. |
| completer.complete(value); |
| } |
| }); |
| if (result != null) { |
| // The code above ran synchronously, and came up with an answer. |
| // Return the SynchronousFuture that we created above. |
| return result; |
| } |
| // The code above hasn't yet run its "then" handler yet. Let's prepare a |
| // completer for it to use when it does run. |
| completer = Completer<T>(); |
| _structuredDataCache[key] = completer.future; |
| return completer.future; |
| } |
| |
| @override |
| void evict(String key) { |
| _stringCache.remove(key); |
| _structuredDataCache.remove(key); |
| } |
| } |
| |
| /// An [AssetBundle] that loads resources using platform messages. |
| class PlatformAssetBundle extends CachingAssetBundle { |
| @override |
| Future<ByteData> load(String key) async { |
| final Uint8List encoded = utf8.encoder.convert(Uri(path: Uri.encodeFull(key)).path); |
| final ByteData asset = |
| await defaultBinaryMessenger.send('flutter/assets', encoded.buffer.asByteData()); // ignore: deprecated_member_use_from_same_package |
| if (asset == null) |
| throw FlutterError('Unable to load asset: $key'); |
| return asset; |
| } |
| } |
| |
| AssetBundle _initRootBundle() { |
| return PlatformAssetBundle(); |
| } |
| |
| /// The [AssetBundle] from which this application was loaded. |
| /// |
| /// The [rootBundle] contains the resources that were packaged with the |
| /// application when it was built. To add resources to the [rootBundle] for your |
| /// application, add them to the `assets` subsection of the `flutter` section of |
| /// your application's `pubspec.yaml` manifest. |
| /// |
| /// For example: |
| /// |
| /// ```yaml |
| /// name: my_awesome_application |
| /// flutter: |
| /// assets: |
| /// - images/hamilton.jpeg |
| /// - images/lafayette.jpeg |
| /// ``` |
| /// |
| /// Rather than using [rootBundle] directly, consider obtaining the |
| /// [AssetBundle] for the current [BuildContext] using [DefaultAssetBundle.of]. |
| /// This layer of indirection lets ancestor widgets substitute a different |
| /// [AssetBundle] at runtime (e.g., for testing or localization) rather than |
| /// directly replying upon the [rootBundle] created at build time. For |
| /// convenience, the [WidgetsApp] or [MaterialApp] widget at the top of the |
| /// widget hierarchy configures the [DefaultAssetBundle] to be the [rootBundle]. |
| /// |
| /// See also: |
| /// |
| /// * [DefaultAssetBundle] |
| /// * [NetworkAssetBundle] |
| final AssetBundle rootBundle = _initRootBundle(); |