1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'package:flutter/material.dart';
6///
7/// @docImport 'app.dart';
8/// @docImport 'fade_in_image.dart';
9/// @docImport 'icon.dart';
10/// @docImport 'transitions.dart';
11library;
12
13import 'dart:async';
14import 'dart:io' show File;
15
16import 'package:flutter/foundation.dart';
17import 'package:flutter/scheduler.dart';
18import 'package:flutter/semantics.dart';
19
20import '../painting/_web_image_info_io.dart'
21 if (dart.library.js_util) '../painting/_web_image_info_web.dart';
22import '_web_image_io.dart' if (dart.library.js_util) '_web_image_web.dart';
23import 'basic.dart';
24import 'binding.dart';
25import 'disposable_build_context.dart';
26import 'framework.dart';
27import 'localizations.dart';
28import 'media_query.dart';
29import 'placeholder.dart';
30import 'scroll_aware_image_provider.dart';
31import 'text.dart';
32import 'ticker_provider.dart';
33
34export 'package:flutter/painting.dart'
35 show
36 AssetImage,
37 ExactAssetImage,
38 FileImage,
39 FilterQuality,
40 ImageConfiguration,
41 ImageInfo,
42 ImageProvider,
43 ImageStream,
44 MemoryImage,
45 NetworkImage;
46
47// Examples can assume:
48// late Widget image;
49// late ImageProvider _image;
50
51/// Creates an [ImageConfiguration] based on the given [BuildContext] (and
52/// optionally size).
53///
54/// This is the object that must be passed to [BoxPainter.paint] and to
55/// [ImageProvider.resolve].
56///
57/// If this is not called from a build method, then it should be reinvoked
58/// whenever the dependencies change, e.g. by calling it from
59/// [State.didChangeDependencies], so that any changes in the environment are
60/// picked up (e.g. if the device pixel ratio changes).
61///
62/// See also:
63///
64/// * [ImageProvider], which has an example showing how this might be used.
65ImageConfiguration createLocalImageConfiguration(BuildContext context, {Size? size}) {
66 return ImageConfiguration(
67 bundle: DefaultAssetBundle.of(context),
68 devicePixelRatio: MediaQuery.maybeDevicePixelRatioOf(context) ?? 1.0,
69 locale: Localizations.maybeLocaleOf(context),
70 textDirection: Directionality.maybeOf(context),
71 size: size,
72 platform: defaultTargetPlatform,
73 );
74}
75
76/// Prefetches an image into the image cache.
77///
78/// Returns a [Future] that will complete when the first image yielded by the
79/// [ImageProvider] is available or failed to load.
80///
81/// If the image is later used by an [Image] or [BoxDecoration] or [FadeInImage],
82/// it will probably be loaded faster. The consumer of the image does not need
83/// to use the same [ImageProvider] instance. The [ImageCache] will find the image
84/// as long as both images share the same key, and the image is held by the
85/// cache.
86///
87/// The cache may refuse to hold the image if it is disabled, the image is too
88/// large, or some other criteria implemented by a custom [ImageCache]
89/// implementation.
90///
91/// The [ImageCache] holds a reference to all images passed to
92/// [ImageCache.putIfAbsent] as long as their [ImageStreamCompleter] has at
93/// least one listener. This method will wait until the end of the frame after
94/// its future completes before releasing its own listener. This gives callers a
95/// chance to listen to the stream if necessary. A caller can determine if the
96/// image ended up in the cache by calling [ImageProvider.obtainCacheStatus]. If
97/// it is only held as [ImageCacheStatus.live], and the caller wishes to keep
98/// the resolved image in memory, the caller should immediately call
99/// `provider.resolve` and add a listener to the returned [ImageStream]. The
100/// image will remain pinned in memory at least until the caller removes its
101/// listener from the stream, even if it would not otherwise fit into the cache.
102///
103/// Callers should be cautious about pinning large images or a large number of
104/// images in memory, as this can result in running out of memory and being
105/// killed by the operating system. The lower the available physical memory, the
106/// more susceptible callers will be to running into OOM issues. These issues
107/// manifest as immediate process death, sometimes with no other error messages.
108///
109/// The [BuildContext] and [Size] are used to select an image configuration
110/// (see [createLocalImageConfiguration]).
111///
112/// The returned future will not complete with error, even if precaching
113/// failed. The `onError` argument can be used to manually handle errors while
114/// pre-caching.
115///
116/// See also:
117///
118/// * [ImageCache], which holds images that may be reused.
119Future<void> precacheImage(
120 ImageProvider provider,
121 BuildContext context, {
122 Size? size,
123 ImageErrorListener? onError,
124}) {
125 final ImageConfiguration config = createLocalImageConfiguration(context, size: size);
126 final Completer<void> completer = Completer<void>();
127 final ImageStream stream = provider.resolve(config);
128 ImageStreamListener? listener;
129 listener = ImageStreamListener(
130 (ImageInfo? image, bool sync) {
131 if (!completer.isCompleted) {
132 completer.complete();
133 }
134 // Give callers until at least the end of the frame to subscribe to the
135 // image stream.
136 // See ImageCache._liveImages
137 SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
138 image?.dispose();
139 stream.removeListener(listener!);
140 }, debugLabel: 'precacheImage.removeListener');
141 },
142 onError: (Object exception, StackTrace? stackTrace) {
143 if (!completer.isCompleted) {
144 completer.complete();
145 }
146 stream.removeListener(listener!);
147 if (onError != null) {
148 onError(exception, stackTrace);
149 } else {
150 FlutterError.reportError(
151 FlutterErrorDetails(
152 context: ErrorDescription('image failed to precache'),
153 library: 'image resource service',
154 exception: exception,
155 stack: stackTrace,
156 silent: true,
157 ),
158 );
159 }
160 },
161 );
162 stream.addListener(listener);
163 return completer.future;
164}
165
166/// Signature used by [Image.frameBuilder] to control the widget that will be
167/// used when an [Image] is built.
168///
169/// The `child` argument contains the default image widget.
170/// Typically, this builder will wrap the `child` widget in some
171/// way and return the wrapped widget. If this builder returns `child` directly,
172/// it will yield the same result as if [Image.frameBuilder] was null.
173///
174/// The `frame` argument specifies the index of the current image frame being
175/// rendered. It will be null before the first image frame is ready, and zero
176/// for the first image frame. For single-frame images, it will never be greater
177/// than zero. For multi-frame images (such as animated GIFs), it will increase
178/// by one every time a new image frame is shown (including when the image
179/// animates in a loop).
180///
181/// The `wasSynchronouslyLoaded` argument specifies whether the image was
182/// available synchronously (on the same
183/// [rendering pipeline frame](rendering/RendererBinding/drawFrame.html) as the
184/// `Image` widget itself was created) and thus able to be painted immediately.
185/// If this is false, then there was one or more rendering pipeline frames where
186/// the image wasn't yet available to be painted. For multi-frame images (such
187/// as animated GIFs), the value of this argument will be the same for all image
188/// frames. In other words, if the first image frame was available immediately,
189/// then this argument will be true for all image frames.
190///
191/// See also:
192///
193/// * [Image.frameBuilder], which makes use of this signature in the [Image]
194/// widget.
195typedef ImageFrameBuilder =
196 Widget Function(BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded);
197
198/// Signature used by [Image.loadingBuilder] to build a representation of the
199/// image's loading progress.
200///
201/// This is useful for images that are incrementally loaded (e.g. over a local
202/// file system or a network), and the application wishes to give the user an
203/// indication of when the image will be displayed.
204///
205/// The `child` argument contains the default image widget and is guaranteed to
206/// be non-null. Typically, this builder will wrap the `child` widget in some
207/// way and return the wrapped widget. If this builder returns `child` directly,
208/// it will yield the same result as if [Image.loadingBuilder] was null.
209///
210/// The `loadingProgress` argument contains the current progress towards loading
211/// the image. This argument will be non-null while the image is loading, but it
212/// will be null in the following cases:
213///
214/// * When the widget is first rendered before any bytes have been loaded.
215/// * When an image has been fully loaded and is available to be painted.
216///
217/// If callers are implementing custom [ImageProvider] and [ImageStream]
218/// instances (which is very rare), it's possible to produce image streams that
219/// continue to fire image chunk events after an image frame has been loaded.
220/// In such cases, the `child` parameter will represent the current
221/// fully-loaded image frame.
222///
223/// See also:
224///
225/// * [Image.loadingBuilder], which makes use of this signature in the [Image]
226/// widget.
227/// * [ImageChunkListener], a lower-level signature for listening to raw
228/// [ImageChunkEvent]s.
229typedef ImageLoadingBuilder =
230 Widget Function(BuildContext context, Widget child, ImageChunkEvent? loadingProgress);
231
232/// Signature used by [Image.errorBuilder] to create a replacement widget to
233/// render instead of the image.
234typedef ImageErrorWidgetBuilder =
235 Widget Function(BuildContext context, Object error, StackTrace? stackTrace);
236
237/// A widget that displays an image.
238///
239/// {@youtube 560 315 https://www.youtube.com/watch?v=7oIAs-0G4mw}
240///
241/// Several constructors are provided for the various ways that an image can be
242/// specified:
243///
244/// * [Image.new], for obtaining an image from an [ImageProvider].
245/// * [Image.asset], for obtaining an image from an [AssetBundle]
246/// using a key.
247/// * [Image.network], for obtaining an image from a URL.
248/// * [Image.file], for obtaining an image from a [File].
249/// * [Image.memory], for obtaining an image from a [Uint8List].
250///
251/// The following image formats are supported: {@macro dart.ui.imageFormats}
252///
253/// To automatically perform pixel-density-aware asset resolution, specify the
254/// image using an [AssetImage] and make sure that a [MaterialApp], [WidgetsApp],
255/// or [MediaQuery] widget exists above the [Image] widget in the widget tree.
256///
257/// The image is painted using [paintImage], which describes the meanings of the
258/// various fields on this class in more detail.
259///
260/// {@tool snippet}
261/// The default constructor can be used with any [ImageProvider], such as a
262/// [NetworkImage], to display an image from the internet.
263///
264/// ![An image of an owl displayed by the image widget](https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg)
265///
266/// ```dart
267/// const Image(
268/// image: NetworkImage('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg'),
269/// )
270/// ```
271/// {@end-tool}
272///
273/// {@tool snippet}
274/// The [Image] Widget also provides several constructors to display different
275/// types of images for convenience. In this example, use the [Image.network]
276/// constructor to display an image from the internet.
277///
278/// ![An image of an owl displayed by the image widget using the shortcut constructor](https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg)
279///
280/// ```dart
281/// Image.network('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg')
282/// ```
283/// {@end-tool}
284///
285/// ## Memory usage
286///
287/// The image is stored in memory in uncompressed form (so that it can be
288/// rendered). Large images will use a lot of memory: a 4K image (3840×2160)
289/// will use over 30MB of RAM (assuming 32 bits per pixel).
290///
291/// This problem is exacerbated by the images being cached in the [ImageCache],
292/// so large images can use memory for even longer than they are displayed.
293///
294/// The [Image.asset], [Image.network], [Image.file], and [Image.memory]
295/// constructors allow a custom decode size to be specified through `cacheWidth`
296/// and `cacheHeight` parameters. The engine will then decode and store the
297/// image at the specified size, instead of the image's natural size.
298///
299/// This can significantly reduce the memory usage. For example, a 4K image that
300/// will be rendered at only 384×216 pixels (one-tenth the horizontal and
301/// vertical dimensions) would only use 330KB if those dimensions are specified
302/// using the `cacheWidth` and `cacheHeight` parameters, a 100-fold reduction in
303/// memory usage.
304///
305/// ## Custom image providers
306///
307/// {@tool dartpad}
308/// In this example, a variant of [NetworkImage] is created that passes all the
309/// [ImageConfiguration] information (locale, platform, size, etc) to the server
310/// using query arguments in the image URL.
311///
312/// ** See code in examples/api/lib/painting/image_provider/image_provider.0.dart **
313/// {@end-tool}
314///
315/// See also:
316///
317/// * [Icon], which shows an image from a font.
318/// * [Ink.image], which is the preferred way to show an image in a
319/// material application (especially if the image is in a [Material] and will
320/// have an [InkWell] on top of it).
321/// * [Image](dart-ui/Image-class.html), the class in the [dart:ui] library.
322/// * Cookbook: [Display images from the internet](https://docs.flutter.dev/cookbook/images/network-image)
323/// * Cookbook: [Fade in images with a placeholder](https://docs.flutter.dev/cookbook/images/fading-in-images)
324class Image extends StatefulWidget {
325 /// Creates a widget that displays an image.
326 ///
327 /// To show an image from the network or from an asset bundle, consider using
328 /// [Image.network] and [Image.asset] respectively.
329 ///
330 /// The `scale` argument specifies the linear scale factor for drawing this
331 /// image at its intended size and applies to both the width and the height.
332 /// {@macro flutter.painting.imageInfo.scale}
333 ///
334 /// Either the [width] and [height] arguments should be specified, or the
335 /// widget should be placed in a context that sets tight layout constraints.
336 /// Otherwise, the image dimensions will change as the image is loaded, which
337 /// will result in ugly layout changes.
338 ///
339 /// {@template flutter.widgets.image.filterQualityParameter}
340 /// Use [filterQuality] to specify the rendering quality of the image.
341 /// {@endtemplate}
342 ///
343 /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
344 const Image({
345 super.key,
346 required this.image,
347 this.frameBuilder,
348 this.loadingBuilder,
349 this.errorBuilder,
350 this.semanticLabel,
351 this.excludeFromSemantics = false,
352 this.width,
353 this.height,
354 this.color,
355 this.opacity,
356 this.colorBlendMode,
357 this.fit,
358 this.alignment = Alignment.center,
359 this.repeat = ImageRepeat.noRepeat,
360 this.centerSlice,
361 this.matchTextDirection = false,
362 this.gaplessPlayback = false,
363 this.isAntiAlias = false,
364 this.filterQuality = FilterQuality.medium,
365 });
366
367 /// Creates a widget that displays an [ImageStream] obtained from the network.
368 ///
369 /// Either the [width] and [height] arguments should be specified, or the
370 /// widget should be placed in a context that sets tight layout constraints.
371 /// Otherwise, the image dimensions will change as the image is loaded, which
372 /// will result in ugly layout changes.
373 ///
374 /// The `scale` argument specifies the linear scale factor for drawing this
375 /// image at its intended size and applies to both the width and the height.
376 /// {@macro flutter.painting.imageInfo.scale}
377 ///
378 /// All network images are cached regardless of HTTP headers.
379 ///
380 /// An optional [headers] argument can be used to send custom HTTP headers
381 /// with the image request.
382 ///
383 /// {@macro flutter.widgets.image.filterQualityParameter}
384 ///
385 /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
386 ///
387 /// If `cacheWidth` or `cacheHeight` are provided, they indicate to the
388 /// engine that the image should be decoded at the specified size. The image
389 /// will be rendered to the constraints of the layout or [width] and [height]
390 /// regardless of these parameters. These parameters are primarily intended
391 /// to reduce the memory usage of [ImageCache].
392 ///
393 /// In the case where the network image is on the Web platform, the [cacheWidth]
394 /// and [cacheHeight] parameters are ignored as the web engine delegates
395 /// image decoding to the web which does not support custom decode sizes.
396 ///
397 /// ### Same-origin policy on Web
398 ///
399 /// Due to browser restriction on Cross-Origin Resource Sharing (CORS),
400 /// Flutter on the Web platform can not fetch images from other origins
401 /// (domain, scheme, or port) than the origin that hosts the app, unless the
402 /// image hosting origin explicitly allows so. CORS errors can be resolved
403 /// by configuring the image hosting server. More information can be
404 /// found at Mozilla's introduction on
405 /// [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)
406 /// and
407 /// [CORS errors](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors).
408 ///
409 /// If it's not possible to configure the host, such as when images are hosted
410 /// on a CDN or from arbitrary URLs, the app can set the
411 /// `webHtmlElementStrategy` parameter of [Image.network] to display the image
412 /// in an HTML element, which bypasses the same-origin policy.
413 ///
414 /// The HTML element is placed in a platform view, and therefore has the
415 /// following drawbacks:
416 ///
417 /// * Suboptimal performance.
418 /// * Can't be captured by screenshot widgets.
419 /// * The `headers` argument must be null or empty.
420 /// * Some image options are ignored, including [opacity], [colorBlendMode],
421 /// [repeat], filtering, and blurring.
422 ///
423 /// By default, this feature is turned off ([WebHtmlElementStrategy.never]).
424 ///
425 /// ### Android Permissions
426 ///
427 /// Images fetched from the network require the internet permission.
428 /// Ensure that all AndroidMainifest.xml variants (especially release) have the internet permission.
429 /// See https://docs.flutter.dev/data-and-backend/networking for more information.
430 Image.network(
431 String src, {
432 super.key,
433 double scale = 1.0,
434 this.frameBuilder,
435 this.loadingBuilder,
436 this.errorBuilder,
437 this.semanticLabel,
438 this.excludeFromSemantics = false,
439 this.width,
440 this.height,
441 this.color,
442 this.opacity,
443 this.colorBlendMode,
444 this.fit,
445 this.alignment = Alignment.center,
446 this.repeat = ImageRepeat.noRepeat,
447 this.centerSlice,
448 this.matchTextDirection = false,
449 this.gaplessPlayback = false,
450 this.filterQuality = FilterQuality.medium,
451 this.isAntiAlias = false,
452 Map<String, String>? headers,
453 int? cacheWidth,
454 int? cacheHeight,
455 WebHtmlElementStrategy webHtmlElementStrategy = WebHtmlElementStrategy.never,
456 }) : image = ResizeImage.resizeIfNeeded(
457 cacheWidth,
458 cacheHeight,
459 NetworkImage(
460 src,
461 scale: scale,
462 headers: headers,
463 webHtmlElementStrategy: webHtmlElementStrategy,
464 ),
465 ),
466 assert(cacheWidth == null || cacheWidth > 0),
467 assert(cacheHeight == null || cacheHeight > 0);
468
469 /// Creates a widget that displays an [ImageStream] obtained from a [File].
470 ///
471 /// The `scale` argument specifies the linear scale factor for drawing this
472 /// image at its intended size and applies to both the width and the height.
473 /// {@macro flutter.painting.imageInfo.scale}
474 ///
475 /// Either the [width] and [height] arguments should be specified, or the
476 /// widget should be placed in a context that sets tight layout constraints.
477 /// Otherwise, the image dimensions will change as the image is loaded, which
478 /// will result in ugly layout changes.
479 ///
480 /// On Android, this may require the
481 /// `android.permission.READ_EXTERNAL_STORAGE` permission.
482 ///
483 /// {@macro flutter.widgets.image.filterQualityParameter}
484 ///
485 /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
486 ///
487 /// If `cacheWidth` or `cacheHeight` are provided, they indicate to the
488 /// engine that the image must be decoded at the specified size. The image
489 /// will be rendered to the constraints of the layout or [width] and [height]
490 /// regardless of these parameters. These parameters are primarily intended
491 /// to reduce the memory usage of [ImageCache].
492 ///
493 /// Loading an image from a file creates an in memory copy of the file,
494 /// which is retained in the [ImageCache]. The underlying file is not
495 /// monitored for changes. If it does change, the application should evict
496 /// the entry from the [ImageCache].
497 ///
498 /// See also:
499 ///
500 /// * [FileImage] provider for evicting the underlying file easily.
501 Image.file(
502 File file, {
503 super.key,
504 double scale = 1.0,
505 this.frameBuilder,
506 this.errorBuilder,
507 this.semanticLabel,
508 this.excludeFromSemantics = false,
509 this.width,
510 this.height,
511 this.color,
512 this.opacity,
513 this.colorBlendMode,
514 this.fit,
515 this.alignment = Alignment.center,
516 this.repeat = ImageRepeat.noRepeat,
517 this.centerSlice,
518 this.matchTextDirection = false,
519 this.gaplessPlayback = false,
520 this.isAntiAlias = false,
521 this.filterQuality = FilterQuality.medium,
522 int? cacheWidth,
523 int? cacheHeight,
524 }) : // FileImage is not supported on Flutter Web therefore neither this method.
525 assert(
526 !kIsWeb,
527 'Image.file is not supported on Flutter Web. '
528 'Consider using either Image.asset or Image.network instead.',
529 ),
530 image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, FileImage(file, scale: scale)),
531 loadingBuilder = null,
532 assert(cacheWidth == null || cacheWidth > 0),
533 assert(cacheHeight == null || cacheHeight > 0);
534
535 // TODO(ianh): Implement the following (see ../services/image_resolution.dart):
536 //
537 // * If [width] and [height] are both specified, and [scale] is not, then
538 // size-aware asset resolution will be attempted also, with the given
539 // dimensions interpreted as logical pixels.
540 //
541 // * If the images have platform, locale, or directionality variants, the
542 // current platform, locale, and directionality are taken into account
543 // during asset resolution as well.
544 /// Creates a widget that displays an [ImageStream] obtained from an asset
545 /// bundle. The key for the image is given by the `name` argument.
546 ///
547 /// The `package` argument must be non-null when displaying an image from a
548 /// package and null otherwise. See the `Assets in packages` section for
549 /// details.
550 ///
551 /// If the `bundle` argument is omitted or null, then the
552 /// [DefaultAssetBundle] will be used.
553 ///
554 /// The `scale` argument specifies the linear scale factor for drawing this
555 /// image at its intended size and applies to both the width and the height.
556 /// {@macro flutter.painting.imageInfo.scale}
557 ///
558 /// By default, the pixel-density-aware asset resolution will be attempted. In
559 /// addition:
560 ///
561 /// * If the `scale` argument is provided and is not null, then the exact
562 /// asset specified will be used. To display an image variant with a specific
563 /// density, the exact path must be provided (e.g. `images/2x/cat.png`).
564 ///
565 /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
566 ///
567 /// If `cacheWidth` or `cacheHeight` are provided, they indicate to the
568 /// engine that the image must be decoded at the specified size. The image
569 /// will be rendered to the constraints of the layout or [width] and [height]
570 /// regardless of these parameters. These parameters are primarily intended
571 /// to reduce the memory usage of [ImageCache].
572 ///
573 /// Either the [width] and [height] arguments should be specified, or the
574 /// widget should be placed in a context that sets tight layout constraints.
575 /// Otherwise, the image dimensions will change as the image is loaded, which
576 /// will result in ugly layout changes.
577 ///
578 /// {@macro flutter.widgets.image.filterQualityParameter}
579 ///
580 /// {@tool snippet}
581 ///
582 /// Suppose that the project's `pubspec.yaml` file contains the following:
583 ///
584 /// ```yaml
585 /// flutter:
586 /// assets:
587 /// - images/cat.png
588 /// - images/2x/cat.png
589 /// - images/3.5x/cat.png
590 /// ```
591 /// {@end-tool}
592 ///
593 /// On a screen with a device pixel ratio of 2.0, the following widget would
594 /// render the `images/2x/cat.png` file:
595 ///
596 /// ```dart
597 /// Image.asset('images/cat.png')
598 /// ```
599 ///
600 /// This corresponds to the file that is in the project's `images/2x/`
601 /// directory with the name `cat.png` (the paths are relative to the
602 /// `pubspec.yaml` file).
603 ///
604 /// On a device with a 4.0 device pixel ratio, the `images/3.5x/cat.png` asset
605 /// would be used. On a device with a 1.0 device pixel ratio, the
606 /// `images/cat.png` resource would be used.
607 ///
608 /// The `images/cat.png` image can be omitted from disk (though it must still
609 /// be present in the manifest). If it is omitted, then on a device with a 1.0
610 /// device pixel ratio, the `images/2x/cat.png` image would be used instead.
611 ///
612 ///
613 /// ## Assets in packages
614 ///
615 /// To create the widget with an asset from a package, the [package] argument
616 /// must be provided. For instance, suppose a package called `my_icons` has
617 /// `icons/heart.png` .
618 ///
619 /// {@tool snippet}
620 /// Then to display the image, use:
621 ///
622 /// ```dart
623 /// Image.asset('icons/heart.png', package: 'my_icons')
624 /// ```
625 /// {@end-tool}
626 ///
627 /// Assets used by the package itself should also be displayed using the
628 /// [package] argument as above.
629 ///
630 /// If the desired asset is specified in the `pubspec.yaml` of the package, it
631 /// is bundled automatically with the app. In particular, assets used by the
632 /// package itself must be specified in its `pubspec.yaml`.
633 ///
634 /// A package can also choose to have assets in its 'lib/' folder that are not
635 /// specified in its `pubspec.yaml`. In this case for those images to be
636 /// bundled, the app has to specify which ones to include. For instance a
637 /// package named `fancy_backgrounds` could have:
638 ///
639 /// lib/backgrounds/background1.png
640 /// lib/backgrounds/background2.png
641 /// lib/backgrounds/background3.png
642 ///
643 /// To include, say the first image, the `pubspec.yaml` of the app should
644 /// specify it in the assets section:
645 ///
646 /// ```yaml
647 /// assets:
648 /// - packages/fancy_backgrounds/backgrounds/background1.png
649 /// ```
650 ///
651 /// The `lib/` is implied, so it should not be included in the asset path.
652 ///
653 ///
654 /// See also:
655 ///
656 /// * [AssetImage], which is used to implement the behavior when the scale is
657 /// omitted.
658 /// * [ExactAssetImage], which is used to implement the behavior when the
659 /// scale is present.
660 /// * <https://docs.flutter.dev/ui/assets/assets-and-images>, an introduction to assets in
661 /// Flutter.
662 Image.asset(
663 String name, {
664 super.key,
665 AssetBundle? bundle,
666 this.frameBuilder,
667 this.errorBuilder,
668 this.semanticLabel,
669 this.excludeFromSemantics = false,
670 double? scale,
671 this.width,
672 this.height,
673 this.color,
674 this.opacity,
675 this.colorBlendMode,
676 this.fit,
677 this.alignment = Alignment.center,
678 this.repeat = ImageRepeat.noRepeat,
679 this.centerSlice,
680 this.matchTextDirection = false,
681 this.gaplessPlayback = false,
682 this.isAntiAlias = false,
683 String? package,
684 this.filterQuality = FilterQuality.medium,
685 int? cacheWidth,
686 int? cacheHeight,
687 }) : image = ResizeImage.resizeIfNeeded(
688 cacheWidth,
689 cacheHeight,
690 scale != null
691 ? ExactAssetImage(name, bundle: bundle, scale: scale, package: package)
692 : AssetImage(name, bundle: bundle, package: package),
693 ),
694 loadingBuilder = null,
695 assert(cacheWidth == null || cacheWidth > 0),
696 assert(cacheHeight == null || cacheHeight > 0);
697
698 /// Creates a widget that displays an [ImageStream] obtained from a [Uint8List].
699 ///
700 /// The `bytes` argument specifies encoded image bytes, which can be encoded
701 /// in any of the following supported image formats:
702 /// {@macro dart.ui.imageFormats}
703 ///
704 /// The `scale` argument specifies the linear scale factor for drawing this
705 /// image at its intended size and applies to both the width and the height.
706 /// {@macro flutter.painting.imageInfo.scale}
707 ///
708 /// This only accepts compressed image formats (e.g. PNG). Uncompressed
709 /// formats like rawRgba (the default format of [dart:ui.Image.toByteData])
710 /// will lead to exceptions.
711 ///
712 /// Either the [width] and [height] arguments should be specified, or the
713 /// widget should be placed in a context that sets tight layout constraints.
714 /// Otherwise, the image dimensions will change as the image is loaded, which
715 /// will result in ugly layout changes.
716 ///
717 /// {@macro flutter.widgets.image.filterQualityParameter}
718 ///
719 /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
720 ///
721 /// If `cacheWidth` or `cacheHeight` are provided, they indicate to the
722 /// engine that the image must be decoded at the specified size. The image
723 /// will be rendered to the constraints of the layout or [width] and [height]
724 /// regardless of these parameters. These parameters are primarily intended
725 /// to reduce the memory usage of [ImageCache].
726 Image.memory(
727 Uint8List bytes, {
728 super.key,
729 double scale = 1.0,
730 this.frameBuilder,
731 this.errorBuilder,
732 this.semanticLabel,
733 this.excludeFromSemantics = false,
734 this.width,
735 this.height,
736 this.color,
737 this.opacity,
738 this.colorBlendMode,
739 this.fit,
740 this.alignment = Alignment.center,
741 this.repeat = ImageRepeat.noRepeat,
742 this.centerSlice,
743 this.matchTextDirection = false,
744 this.gaplessPlayback = false,
745 this.isAntiAlias = false,
746 this.filterQuality = FilterQuality.medium,
747 int? cacheWidth,
748 int? cacheHeight,
749 }) : image = ResizeImage.resizeIfNeeded(
750 cacheWidth,
751 cacheHeight,
752 MemoryImage(bytes, scale: scale),
753 ),
754 loadingBuilder = null,
755 assert(cacheWidth == null || cacheWidth > 0),
756 assert(cacheHeight == null || cacheHeight > 0);
757
758 /// The image to display.
759 final ImageProvider image;
760
761 /// A builder function responsible for creating the widget that represents
762 /// this image.
763 ///
764 /// If this is null, this widget will display an image that is painted as
765 /// soon as the first image frame is available (and will appear to "pop" in
766 /// if it becomes available asynchronously). Callers might use this builder to
767 /// add effects to the image (such as fading the image in when it becomes
768 /// available) or to display a placeholder widget while the image is loading.
769 ///
770 /// For more information on how to interpret the arguments that are passed to
771 /// this builder, see the documentation on [ImageFrameBuilder].
772 ///
773 /// To have finer-grained control over the way that an image's loading
774 /// progress is communicated to the user, see [loadingBuilder].
775 ///
776 /// ## Chaining with [loadingBuilder]
777 ///
778 /// If a [loadingBuilder] has _also_ been specified for an image, the two
779 /// builders will be chained together: the _result_ of this builder will
780 /// be passed as the `child` argument to the [loadingBuilder]. For example,
781 /// consider the following builders used in conjunction:
782 ///
783 /// {@template flutter.widgets.Image.frameBuilder.chainedBuildersExample}
784 /// ```dart
785 /// Image(
786 /// image: _image,
787 /// frameBuilder: (BuildContext context, Widget child, int? frame, bool? wasSynchronouslyLoaded) {
788 /// return Padding(
789 /// padding: const EdgeInsets.all(8.0),
790 /// child: child,
791 /// );
792 /// },
793 /// loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
794 /// return Center(child: child);
795 /// },
796 /// )
797 /// ```
798 ///
799 /// In this example, the widget hierarchy will contain the following:
800 ///
801 /// ```dart
802 /// Center(
803 /// child: Padding(
804 /// padding: const EdgeInsets.all(8.0),
805 /// child: image,
806 /// ),
807 /// ),
808 /// ```
809 /// {@endtemplate}
810 ///
811 /// {@tool dartpad}
812 /// The following sample demonstrates how to use this builder to implement an
813 /// image that fades in once it's been loaded.
814 ///
815 /// This sample contains a limited subset of the functionality that the
816 /// [FadeInImage] widget provides out of the box.
817 ///
818 /// ** See code in examples/api/lib/widgets/image/image.frame_builder.0.dart **
819 /// {@end-tool}
820 final ImageFrameBuilder? frameBuilder;
821
822 /// A builder that specifies the widget to display to the user while an image
823 /// is still loading.
824 ///
825 /// If this is null, and the image is loaded incrementally (e.g. over a
826 /// network), the user will receive no indication of the progress as the
827 /// bytes of the image are loaded.
828 ///
829 /// For more information on how to interpret the arguments that are passed to
830 /// this builder, see the documentation on [ImageLoadingBuilder].
831 ///
832 /// ## Performance implications
833 ///
834 /// If a [loadingBuilder] is specified for an image, the [Image] widget is
835 /// likely to be rebuilt on every
836 /// [rendering pipeline frame](rendering/RendererBinding/drawFrame.html) until
837 /// the image has loaded. This is useful for cases such as displaying a loading
838 /// progress indicator, but for simpler cases such as displaying a placeholder
839 /// widget that doesn't depend on the loading progress (e.g. static "loading"
840 /// text), [frameBuilder] will likely work and not incur as much cost.
841 ///
842 /// ## Chaining with [frameBuilder]
843 ///
844 /// If a [frameBuilder] has _also_ been specified for an image, the two
845 /// builders will be chained together: the `child` argument to this
846 /// builder will contain the _result_ of the [frameBuilder]. For example,
847 /// consider the following builders used in conjunction:
848 ///
849 /// {@macro flutter.widgets.Image.frameBuilder.chainedBuildersExample}
850 ///
851 /// {@tool dartpad}
852 /// The following sample uses [loadingBuilder] to show a
853 /// [CircularProgressIndicator] while an image loads over the network.
854 ///
855 /// ** See code in examples/api/lib/widgets/image/image.loading_builder.0.dart **
856 /// {@end-tool}
857 ///
858 /// Run against a real-world image on a slow network, the previous example
859 /// renders the following loading progress indicator while the image loads
860 /// before rendering the completed image.
861 ///
862 /// {@animation 400 400 https://flutter.github.io/assets-for-api-docs/assets/widgets/loading_progress_image.mp4}
863 final ImageLoadingBuilder? loadingBuilder;
864
865 /// A builder function that is called if an error occurs during image loading.
866 ///
867 /// If this builder is not provided, any exceptions will be reported to
868 /// [FlutterError.onError]. If it is provided, the caller should either handle
869 /// the exception by providing a replacement widget, or rethrow the exception.
870 ///
871 /// {@tool dartpad}
872 /// The following sample uses [errorBuilder] to show a '😢' in place of the
873 /// image that fails to load, and prints the error to the console.
874 ///
875 /// ** See code in examples/api/lib/widgets/image/image.error_builder.0.dart **
876 /// {@end-tool}
877 final ImageErrorWidgetBuilder? errorBuilder;
878
879 /// If non-null, require the image to have this width (in logical pixels).
880 ///
881 /// If null, the image will pick a size that best preserves its intrinsic
882 /// aspect ratio.
883 ///
884 /// It is strongly recommended that either both the [width] and the [height]
885 /// be specified, or that the widget be placed in a context that sets tight
886 /// layout constraints, so that the image does not change size as it loads.
887 /// Consider using [fit] to adapt the image's rendering to fit the given width
888 /// and height if the exact image dimensions are not known in advance.
889 final double? width;
890
891 /// If non-null, require the image to have this height (in logical pixels).
892 ///
893 /// If null, the image will pick a size that best preserves its intrinsic
894 /// aspect ratio.
895 ///
896 /// It is strongly recommended that either both the [width] and the [height]
897 /// be specified, or that the widget be placed in a context that sets tight
898 /// layout constraints, so that the image does not change size as it loads.
899 /// Consider using [fit] to adapt the image's rendering to fit the given width
900 /// and height if the exact image dimensions are not known in advance.
901 final double? height;
902
903 /// If non-null, this color is blended with each image pixel using [colorBlendMode].
904 final Color? color;
905
906 /// If non-null, the value from the [Animation] is multiplied with the opacity
907 /// of each image pixel before painting onto the canvas.
908 ///
909 /// This is more efficient than using [FadeTransition] to change the opacity
910 /// of an image, since this avoids creating a new composited layer. Composited
911 /// layers may double memory usage as the image is painted onto an offscreen
912 /// render target.
913 ///
914 /// See also:
915 ///
916 /// * [AlwaysStoppedAnimation], which allows you to create an [Animation]
917 /// from a single opacity value.
918 final Animation<double>? opacity;
919
920 /// The rendering quality of the image.
921 ///
922 /// {@template flutter.widgets.image.filterQuality}
923 /// If the image is of a high quality and its pixels are perfectly aligned
924 /// with the physical screen pixels, extra quality enhancement may not be
925 /// necessary. If so, then [FilterQuality.none] would be the most efficient.
926 ///
927 /// If the pixels are not perfectly aligned with the screen pixels, or if the
928 /// image itself is of a low quality, [FilterQuality.none] may produce
929 /// undesirable artifacts. Consider using other [FilterQuality] values to
930 /// improve the rendered image quality in this case. Pixels may be misaligned
931 /// with the screen pixels as a result of transforms or scaling.
932 ///
933 /// Defaults to [FilterQuality.medium].
934 ///
935 /// See also:
936 ///
937 /// * [FilterQuality], the enum containing all possible filter quality
938 /// options.
939 /// {@endtemplate}
940 final FilterQuality filterQuality;
941
942 /// Used to combine [color] with this image.
943 ///
944 /// The default is [BlendMode.srcIn]. In terms of the blend mode, [color] is
945 /// the source and this image is the destination.
946 ///
947 /// See also:
948 ///
949 /// * [BlendMode], which includes an illustration of the effect of each blend mode.
950 final BlendMode? colorBlendMode;
951
952 /// How to inscribe the image into the space allocated during layout.
953 ///
954 /// The default varies based on the other fields. See the discussion at
955 /// [paintImage].
956 final BoxFit? fit;
957
958 /// How to align the image within its bounds.
959 ///
960 /// The alignment aligns the given position in the image to the given position
961 /// in the layout bounds. For example, an [Alignment] alignment of (-1.0,
962 /// -1.0) aligns the image to the top-left corner of its layout bounds, while an
963 /// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
964 /// image with the bottom right corner of its layout bounds. Similarly, an
965 /// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
966 /// middle of the bottom edge of its layout bounds.
967 ///
968 /// To display a subpart of an image, consider using a [CustomPainter] and
969 /// [Canvas.drawImageRect].
970 ///
971 /// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
972 /// [AlignmentDirectional]), then an ambient [Directionality] widget
973 /// must be in scope.
974 ///
975 /// Defaults to [Alignment.center].
976 ///
977 /// See also:
978 ///
979 /// * [Alignment], a class with convenient constants typically used to
980 /// specify an [AlignmentGeometry].
981 /// * [AlignmentDirectional], like [Alignment] for specifying alignments
982 /// relative to text direction.
983 final AlignmentGeometry alignment;
984
985 /// How to paint any portions of the layout bounds not covered by the image.
986 final ImageRepeat repeat;
987
988 /// The center slice for a nine-patch image.
989 ///
990 /// The region of the image inside the center slice will be stretched both
991 /// horizontally and vertically to fit the image into its destination. The
992 /// region of the image above and below the center slice will be stretched
993 /// only horizontally and the region of the image to the left and right of
994 /// the center slice will be stretched only vertically.
995 final Rect? centerSlice;
996
997 /// Whether to paint the image in the direction of the [TextDirection].
998 ///
999 /// If this is true, then in [TextDirection.ltr] contexts, the image will be
1000 /// drawn with its origin in the top left (the "normal" painting direction for
1001 /// images); and in [TextDirection.rtl] contexts, the image will be drawn with
1002 /// a scaling factor of -1 in the horizontal direction so that the origin is
1003 /// in the top right.
1004 ///
1005 /// This is occasionally used with images in right-to-left environments, for
1006 /// images that were designed for left-to-right locales. Be careful, when
1007 /// using this, to not flip images with integral shadows, text, or other
1008 /// effects that will look incorrect when flipped.
1009 ///
1010 /// If this is true, there must be an ambient [Directionality] widget in
1011 /// scope.
1012 final bool matchTextDirection;
1013
1014 /// Whether to continue showing the old image (true), or briefly show nothing
1015 /// (false), when the image provider changes. The default value is false.
1016 ///
1017 /// ## Design discussion
1018 ///
1019 /// ### Why is the default value of [gaplessPlayback] false?
1020 ///
1021 /// Having the default value of [gaplessPlayback] be false helps prevent
1022 /// situations where stale or misleading information might be presented.
1023 /// Consider the following case:
1024 ///
1025 /// We have constructed a 'Person' widget that displays an avatar [Image] of
1026 /// the currently loaded person along with their name. We could request for a
1027 /// new person to be loaded into the widget at any time. Suppose we have a
1028 /// person currently loaded and the widget loads a new person. What happens
1029 /// if the [Image] fails to load?
1030 ///
1031 /// * Option A ([gaplessPlayback] = false): The new person's name is coupled
1032 /// with a blank image.
1033 ///
1034 /// * Option B ([gaplessPlayback] = true): The widget displays the avatar of
1035 /// the previous person and the name of the newly loaded person.
1036 ///
1037 /// This is why the default value is false. Most of the time, when you change
1038 /// the image provider you're not just changing the image, you're removing the
1039 /// old widget and adding a new one and not expecting them to have any
1040 /// relationship. With [gaplessPlayback] on you might accidentally break this
1041 /// expectation and re-use the old widget.
1042 final bool gaplessPlayback;
1043
1044 /// A Semantic description of the image.
1045 ///
1046 /// Used to provide a description of the image to TalkBack on Android, and
1047 /// VoiceOver on iOS.
1048 final String? semanticLabel;
1049
1050 /// Whether to exclude this image from semantics.
1051 ///
1052 /// Useful for images which do not contribute meaningful information to an
1053 /// application.
1054 final bool excludeFromSemantics;
1055
1056 /// Whether to paint the image with anti-aliasing.
1057 ///
1058 /// Anti-aliasing alleviates the sawtooth artifact when the image is rotated.
1059 final bool isAntiAlias;
1060
1061 @override
1062 State<Image> createState() => _ImageState();
1063
1064 @override
1065 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1066 super.debugFillProperties(properties);
1067 properties.add(DiagnosticsProperty<ImageProvider>('image', image));
1068 properties.add(DiagnosticsProperty<Function>('frameBuilder', frameBuilder));
1069 properties.add(DiagnosticsProperty<Function>('loadingBuilder', loadingBuilder));
1070 properties.add(DoubleProperty('width', width, defaultValue: null));
1071 properties.add(DoubleProperty('height', height, defaultValue: null));
1072 properties.add(ColorProperty('color', color, defaultValue: null));
1073 properties.add(DiagnosticsProperty<Animation<double>?>('opacity', opacity, defaultValue: null));
1074 properties.add(EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null));
1075 properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null));
1076 properties.add(
1077 DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null),
1078 );
1079 properties.add(EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
1080 properties.add(DiagnosticsProperty<Rect>('centerSlice', centerSlice, defaultValue: null));
1081 properties.add(
1082 FlagProperty('matchTextDirection', value: matchTextDirection, ifTrue: 'match text direction'),
1083 );
1084 properties.add(StringProperty('semanticLabel', semanticLabel, defaultValue: null));
1085 properties.add(DiagnosticsProperty<bool>('this.excludeFromSemantics', excludeFromSemantics));
1086 properties.add(EnumProperty<FilterQuality>('filterQuality', filterQuality));
1087 }
1088}
1089
1090class _ImageState extends State<Image> with WidgetsBindingObserver {
1091 ImageStream? _imageStream;
1092 ImageInfo? _imageInfo;
1093 ImageChunkEvent? _loadingProgress;
1094 bool _isListeningToStream = false;
1095 late bool _invertColors;
1096 int? _frameNumber;
1097 bool _wasSynchronouslyLoaded = false;
1098 late DisposableBuildContext<State<Image>> _scrollAwareContext;
1099 Object? _lastException;
1100 StackTrace? _lastStack;
1101 ImageStreamCompleterHandle? _completerHandle;
1102
1103 @override
1104 void initState() {
1105 super.initState();
1106 WidgetsBinding.instance.addObserver(this);
1107 _scrollAwareContext = DisposableBuildContext<State<Image>>(this);
1108 }
1109
1110 @override
1111 void dispose() {
1112 assert(_imageStream != null);
1113 WidgetsBinding.instance.removeObserver(this);
1114 _stopListeningToStream();
1115 _completerHandle?.dispose();
1116 _scrollAwareContext.dispose();
1117 _replaceImage(info: null);
1118 super.dispose();
1119 }
1120
1121 @override
1122 void didChangeDependencies() {
1123 _updateInvertColors();
1124 _resolveImage();
1125
1126 if (TickerMode.of(context)) {
1127 _listenToStream();
1128 } else {
1129 _stopListeningToStream(keepStreamAlive: true);
1130 }
1131
1132 super.didChangeDependencies();
1133 }
1134
1135 @override
1136 void didUpdateWidget(Image oldWidget) {
1137 super.didUpdateWidget(oldWidget);
1138 if (_isListeningToStream &&
1139 (widget.loadingBuilder == null) != (oldWidget.loadingBuilder == null)) {
1140 final ImageStreamListener oldListener = _getListener();
1141 _imageStream!.addListener(_getListener(recreateListener: true));
1142 _imageStream!.removeListener(oldListener);
1143 }
1144 if (widget.image != oldWidget.image) {
1145 _resolveImage();
1146 }
1147 }
1148
1149 @override
1150 void didChangeAccessibilityFeatures() {
1151 super.didChangeAccessibilityFeatures();
1152 setState(() {
1153 _updateInvertColors();
1154 });
1155 }
1156
1157 @override
1158 void reassemble() {
1159 _resolveImage(); // in case the image cache was flushed
1160 super.reassemble();
1161 }
1162
1163 void _updateInvertColors() {
1164 _invertColors =
1165 MediaQuery.maybeInvertColorsOf(context) ??
1166 SemanticsBinding.instance.accessibilityFeatures.invertColors;
1167 }
1168
1169 void _resolveImage() {
1170 final ScrollAwareImageProvider provider = ScrollAwareImageProvider<Object>(
1171 context: _scrollAwareContext,
1172 imageProvider: widget.image,
1173 );
1174 final ImageStream newStream = provider.resolve(
1175 createLocalImageConfiguration(
1176 context,
1177 size: widget.width != null && widget.height != null
1178 ? Size(widget.width!, widget.height!)
1179 : null,
1180 ),
1181 );
1182 _updateSourceStream(newStream);
1183 }
1184
1185 ImageStreamListener? _imageStreamListener;
1186 ImageStreamListener _getListener({bool recreateListener = false}) {
1187 if (_imageStreamListener == null || recreateListener) {
1188 _lastException = null;
1189 _lastStack = null;
1190 _imageStreamListener = ImageStreamListener(
1191 _handleImageFrame,
1192 onChunk: widget.loadingBuilder == null ? null : _handleImageChunk,
1193 onError: widget.errorBuilder != null || kDebugMode
1194 ? (Object error, StackTrace? stackTrace) {
1195 setState(() {
1196 _lastException = error;
1197 _lastStack = stackTrace;
1198 });
1199 assert(() {
1200 if (widget.errorBuilder == null) {
1201 // ignore: only_throw_errors, since we're just proxying the error.
1202 throw error; // Ensures the error message is printed to the console.
1203 }
1204 return true;
1205 }());
1206 }
1207 : null,
1208 );
1209 }
1210 return _imageStreamListener!;
1211 }
1212
1213 void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) {
1214 setState(() {
1215 _replaceImage(info: imageInfo);
1216 _loadingProgress = null;
1217 _lastException = null;
1218 _lastStack = null;
1219 _frameNumber = _frameNumber == null ? 0 : _frameNumber! + 1;
1220 _wasSynchronouslyLoaded = _wasSynchronouslyLoaded | synchronousCall;
1221 });
1222 }
1223
1224 void _handleImageChunk(ImageChunkEvent event) {
1225 assert(widget.loadingBuilder != null);
1226 setState(() {
1227 _loadingProgress = event;
1228 _lastException = null;
1229 _lastStack = null;
1230 });
1231 }
1232
1233 void _replaceImage({required ImageInfo? info}) {
1234 final ImageInfo? oldImageInfo = _imageInfo;
1235 SchedulerBinding.instance.addPostFrameCallback(
1236 (_) => oldImageInfo?.dispose(),
1237 debugLabel: 'Image.disposeOldInfo',
1238 );
1239 _imageInfo = info;
1240 }
1241
1242 // Updates _imageStream to newStream, and moves the stream listener
1243 // registration from the old stream to the new stream (if a listener was
1244 // registered).
1245 void _updateSourceStream(ImageStream newStream) {
1246 if (_imageStream?.key == newStream.key) {
1247 return;
1248 }
1249
1250 if (_isListeningToStream) {
1251 _imageStream!.removeListener(_getListener());
1252 }
1253
1254 if (!widget.gaplessPlayback) {
1255 setState(() {
1256 _replaceImage(info: null);
1257 });
1258 }
1259
1260 setState(() {
1261 _loadingProgress = null;
1262 _frameNumber = null;
1263 _wasSynchronouslyLoaded = false;
1264 });
1265
1266 _imageStream = newStream;
1267 if (_isListeningToStream) {
1268 _imageStream!.addListener(_getListener());
1269 }
1270 }
1271
1272 void _listenToStream() {
1273 if (_isListeningToStream) {
1274 return;
1275 }
1276
1277 _imageStream!.addListener(_getListener());
1278 _completerHandle?.dispose();
1279 _completerHandle = null;
1280
1281 _isListeningToStream = true;
1282 }
1283
1284 /// Stops listening to the image stream, if this state object has attached a
1285 /// listener.
1286 ///
1287 /// If the listener from this state is the last listener on the stream, the
1288 /// stream will be disposed. To keep the stream alive, set `keepStreamAlive`
1289 /// to true, which create [ImageStreamCompleterHandle] to keep the completer
1290 /// alive and is compatible with the [TickerMode] being off.
1291 void _stopListeningToStream({bool keepStreamAlive = false}) {
1292 if (!_isListeningToStream) {
1293 return;
1294 }
1295
1296 if (keepStreamAlive && _completerHandle == null && _imageStream?.completer != null) {
1297 _completerHandle = _imageStream!.completer!.keepAlive();
1298 }
1299
1300 // It's almost time to remove the last listener, which triggers the
1301 // disposal. But before that, add an ephemeral listener to potentially
1302 // suppress errors.
1303 //
1304 // Reason: When an app provides an `Image` widget with an `errorBuilder`, it
1305 // expects the widget to never report errors through `FlutterError` in any
1306 // cases. This is hard if the stream fails after the disposal, because an
1307 // image stream must have no listeners to be disposed, which then has
1308 // nothing to suppress the errors. This is solve with the help of an
1309 // ephemeral listener, which also suppresses the error but does not hinder
1310 // disposal. For more details, see
1311 // https://github.com/flutter/flutter/issues/97077 .
1312 if (_imageStream!.completer != null && widget.errorBuilder != null) {
1313 _imageStream!.completer!.addEphemeralErrorListener((
1314 Object exception,
1315 StackTrace? stackTrace,
1316 ) {
1317 // Intentionally blank.
1318 });
1319 }
1320 _imageStream!.removeListener(_getListener());
1321 _isListeningToStream = false;
1322 }
1323
1324 Widget _debugBuildErrorWidget(BuildContext context, Object error) {
1325 return Stack(
1326 alignment: Alignment.center,
1327 children: <Widget>[
1328 const Positioned.fill(child: Placeholder(color: Color(0xCF8D021F))),
1329 Padding(
1330 padding: const EdgeInsets.all(4.0),
1331 child: FittedBox(
1332 child: Text(
1333 '$error',
1334 textAlign: TextAlign.center,
1335 textDirection: TextDirection.ltr,
1336 style: const TextStyle(shadows: <Shadow>[Shadow(blurRadius: 1.0)]),
1337 ),
1338 ),
1339 ),
1340 ],
1341 );
1342 }
1343
1344 @override
1345 Widget build(BuildContext context) {
1346 if (_lastException != null) {
1347 if (widget.errorBuilder != null) {
1348 return widget.errorBuilder!(context, _lastException!, _lastStack);
1349 }
1350 if (kDebugMode) {
1351 return _debugBuildErrorWidget(context, _lastException!);
1352 }
1353 }
1354
1355 late Widget result;
1356 if (_imageInfo case final WebImageInfo webImage) {
1357 // TODO(harryterkelsen): Support the remaining properties that are
1358 // supported by `RawImage` but not `RawWebImage`. See the following issue
1359 // above for a discussion of the missing properties and suggestions for
1360 // how they can be implemented, https://github.com/flutter/flutter/issues/159565.
1361 result = RawWebImage(
1362 image: webImage,
1363 debugImageLabel: _imageInfo?.debugLabel,
1364 width: widget.width,
1365 height: widget.height,
1366 fit: widget.fit,
1367 alignment: widget.alignment,
1368 matchTextDirection: widget.matchTextDirection,
1369 );
1370 } else {
1371 result = RawImage(
1372 // Do not clone the image, because RawImage is a stateless wrapper.
1373 // The image will be disposed by this state object when it is not needed
1374 // anymore, such as when it is unmounted or when the image stream pushes
1375 // a new image.
1376 image: _imageInfo?.image,
1377 debugImageLabel: _imageInfo?.debugLabel,
1378 width: widget.width,
1379 height: widget.height,
1380 scale: _imageInfo?.scale ?? 1.0,
1381 color: widget.color,
1382 opacity: widget.opacity,
1383 colorBlendMode: widget.colorBlendMode,
1384 fit: widget.fit,
1385 alignment: widget.alignment,
1386 repeat: widget.repeat,
1387 centerSlice: widget.centerSlice,
1388 matchTextDirection: widget.matchTextDirection,
1389 invertColors: _invertColors,
1390 isAntiAlias: widget.isAntiAlias,
1391 filterQuality: widget.filterQuality,
1392 );
1393 }
1394
1395 if (!widget.excludeFromSemantics) {
1396 result = Semantics(
1397 container: widget.semanticLabel != null,
1398 image: true,
1399 label: widget.semanticLabel ?? '',
1400 child: result,
1401 );
1402 }
1403
1404 if (widget.frameBuilder != null) {
1405 result = widget.frameBuilder!(context, result, _frameNumber, _wasSynchronouslyLoaded);
1406 }
1407
1408 if (widget.loadingBuilder != null) {
1409 result = widget.loadingBuilder!(context, result, _loadingProgress);
1410 }
1411
1412 return result;
1413 }
1414
1415 @override
1416 void debugFillProperties(DiagnosticPropertiesBuilder description) {
1417 super.debugFillProperties(description);
1418 description.add(DiagnosticsProperty<ImageStream>('stream', _imageStream));
1419 description.add(DiagnosticsProperty<ImageInfo>('pixels', _imageInfo));
1420 description.add(DiagnosticsProperty<ImageChunkEvent>('loadingProgress', _loadingProgress));
1421 description.add(DiagnosticsProperty<int>('frameNumber', _frameNumber));
1422 description.add(DiagnosticsProperty<bool>('wasSynchronouslyLoaded', _wasSynchronouslyLoaded));
1423 }
1424}
1425