From c80d2482f9bad0d13a83106dc655a6854833ee8a Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Tue, 1 Aug 2017 17:36:38 -0700 Subject: [PATCH] An API that prefetches an image into the cache (#11471) --- packages/flutter/lib/src/widgets/image.dart | 29 ++++++++++++++++ packages/flutter/test/widgets/image_test.dart | 34 +++++++++++++++---- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/packages/flutter/lib/src/widgets/image.dart b/packages/flutter/lib/src/widgets/image.dart index 024c2a3f8b..e30b531d4d 100644 --- a/packages/flutter/lib/src/widgets/image.dart +++ b/packages/flutter/lib/src/widgets/image.dart @@ -2,6 +2,7 @@ // 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:io' show File; import 'dart:typed_data'; @@ -44,6 +45,34 @@ ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size si ); } +/// Prefetches an image into the image cache. +/// +/// Returns a [Future] that will complete when the first image yielded by the +/// [ImageProvider] is available. +/// +/// If the image is later used by an [Image] or [BoxDecoration] or [FadeInImage], +/// it will probably be loaded faster. The consumer of the image does not need +/// to use the same [ImageProvider] instance. The [ImageCache] will find the image +/// as long as both images share the same key. +/// +/// The [BuildContext] and [Size] are used to select an image configuration +/// (see [createLocalImageConfiguration]). +/// +/// See also: +/// +/// * [ImageCache], which holds images that may be reused. +Future precacheImage(ImageProvider provider, BuildContext context, { Size size }) { + final ImageConfiguration config = createLocalImageConfiguration(context, size: size); + final Completer completer = new Completer(); + final ImageStream stream = provider.resolve(config); + void listener(ImageInfo image, bool sync) { + completer.complete(); + } + stream.addListener(listener); + completer.future.then((Null _) { stream.removeListener(listener); }); + return completer.future; +} + /// A widget that displays an image. /// /// Several constructors are provided for the various ways that an image can be diff --git a/packages/flutter/test/widgets/image_test.dart b/packages/flutter/test/widgets/image_test.dart index 0cdb4aa98c..5047e23a15 100644 --- a/packages/flutter/test/widgets/image_test.dart +++ b/packages/flutter/test/widgets/image_test.dart @@ -196,7 +196,7 @@ void main() { ) ); - expect(imageProvider._configuration.devicePixelRatio, 5.0); + expect(imageProvider._lastResolvedConfiguration.devicePixelRatio, 5.0); // This is the same widget hierarchy as before except that the // two MediaQuery objects have exchanged places. The imageProvider @@ -222,7 +222,7 @@ void main() { ) ); - expect(imageProvider._configuration.devicePixelRatio, 10.0); + expect(imageProvider._lastResolvedConfiguration.devicePixelRatio, 10.0); }); testWidgets('Verify ImageProvider configuration inheritance again', (WidgetTester tester) async { @@ -259,7 +259,7 @@ void main() { ) ); - expect(imageProvider._configuration.devicePixelRatio, 5.0); + expect(imageProvider._lastResolvedConfiguration.devicePixelRatio, 5.0); await tester.pumpWidget( new Row( @@ -287,7 +287,7 @@ void main() { ) ); - expect(imageProvider._configuration.devicePixelRatio, 10.0); + expect(imageProvider._lastResolvedConfiguration.devicePixelRatio, 10.0); }); testWidgets('Verify Image stops listening to ImageStream', (WidgetTester tester) async { @@ -318,11 +318,33 @@ void main() { expect(renderer.color, const Color(0xFF00FF00)); expect(renderer.colorBlendMode, BlendMode.clear); }); + + testWidgets('Precache', (WidgetTester tester) async { + final TestImageProvider provider = new TestImageProvider(); + Future precache; + await tester.pumpWidget( + new Builder( + builder: (BuildContext context) { + precache = precacheImage(provider, context); + return new Container(); + } + ) + ); + provider.complete(); + await precache; + expect(provider._lastResolvedConfiguration, isNotNull); + + // Check that a second resolve of the same image is synchronous. + final ImageStream stream = provider.resolve(provider._lastResolvedConfiguration); + bool isSync; + stream.addListener((ImageInfo image, bool sync) { isSync = sync; }); + expect(isSync, isTrue); + }); } class TestImageProvider extends ImageProvider { final Completer _completer = new Completer(); - ImageConfiguration _configuration; + ImageConfiguration _lastResolvedConfiguration; @override Future obtainKey(ImageConfiguration configuration) { @@ -331,7 +353,7 @@ class TestImageProvider extends ImageProvider { @override ImageStream resolve(ImageConfiguration configuration) { - _configuration = configuration; + _lastResolvedConfiguration = configuration; return super.resolve(configuration); }