Sample code for ImageProvider (#131952)
Also: - minor improvements to documentation - wrap one of our test error messages in a manner more consistent with other messages
This commit is contained in:
parent
702b78c6be
commit
9c8f3950e3
@ -1006,14 +1006,11 @@ Future<void> _runFrameworkTests() async {
|
||||
await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'));
|
||||
await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable'));
|
||||
const String httpClientWarning =
|
||||
'Warning: At least one test in this suite creates an HttpClient. When\n'
|
||||
'running a test suite that uses TestWidgetsFlutterBinding, all HTTP\n'
|
||||
'requests will return status code 400, and no network request will\n'
|
||||
'actually be made. Any test expecting a real network connection and\n'
|
||||
'status code will fail.\n'
|
||||
'To test code that needs an HttpClient, provide your own HttpClient\n'
|
||||
'implementation to the code under test, so that your test can\n'
|
||||
'consistently provide a testable response to the code under test.';
|
||||
'Warning: At least one test in this suite creates an HttpClient. When running a test suite that uses\n'
|
||||
'TestWidgetsFlutterBinding, all HTTP requests will return status code 400, and no network request\n'
|
||||
'will actually be made. Any test expecting a real network connection and status code will fail.\n'
|
||||
'To test code that needs an HttpClient, provide your own HttpClient implementation to the code under\n'
|
||||
'test, so that your test can consistently provide a testable response to the code under test.';
|
||||
await _runFlutterTest(
|
||||
path.join(flutterRoot, 'packages', 'flutter_test'),
|
||||
script: path.join('test', 'bindings_test_failure.dart'),
|
||||
|
104
examples/api/lib/painting/image_provider/image_provider.0.dart
Normal file
104
examples/api/lib/painting/image_provider/image_provider.0.dart
Normal file
@ -0,0 +1,104 @@
|
||||
// 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:io';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@immutable
|
||||
class CustomNetworkImage extends ImageProvider<Uri> {
|
||||
const CustomNetworkImage(this.url);
|
||||
|
||||
final String url;
|
||||
|
||||
@override
|
||||
Future<Uri> obtainKey(ImageConfiguration configuration) {
|
||||
final Uri result = Uri.parse(url).replace(
|
||||
queryParameters: <String, String>{
|
||||
'dpr': '${configuration.devicePixelRatio}',
|
||||
'locale': '${configuration.locale?.toLanguageTag()}',
|
||||
'platform': '${configuration.platform?.name}',
|
||||
'width': '${configuration.size?.width}',
|
||||
'height': '${configuration.size?.height}',
|
||||
'bidi': '${configuration.textDirection?.name}',
|
||||
},
|
||||
);
|
||||
return SynchronousFuture<Uri>(result);
|
||||
}
|
||||
|
||||
static HttpClient get _httpClient {
|
||||
HttpClient? client;
|
||||
assert(() {
|
||||
if (debugNetworkImageHttpClientProvider != null) {
|
||||
client = debugNetworkImageHttpClientProvider!();
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
return client ?? HttpClient()..autoUncompress = false;
|
||||
}
|
||||
|
||||
@override
|
||||
ImageStreamCompleter loadImage(Uri key, ImageDecoderCallback decode) {
|
||||
final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
|
||||
debugPrint('Fetching "$key"...');
|
||||
return MultiFrameImageStreamCompleter(
|
||||
codec: _httpClient.getUrl(key)
|
||||
.then<HttpClientResponse>((HttpClientRequest request) => request.close())
|
||||
.then<Uint8List>((HttpClientResponse response) {
|
||||
return consolidateHttpClientResponseBytes(
|
||||
response,
|
||||
onBytesReceived: (int cumulative, int? total) {
|
||||
chunkEvents.add(ImageChunkEvent(
|
||||
cumulativeBytesLoaded: cumulative,
|
||||
expectedTotalBytes: total,
|
||||
));
|
||||
},
|
||||
);
|
||||
})
|
||||
.catchError((Object e, StackTrace stack) {
|
||||
scheduleMicrotask(() {
|
||||
PaintingBinding.instance.imageCache.evict(key);
|
||||
});
|
||||
return Future<Uint8List>.error(e, stack);
|
||||
})
|
||||
.whenComplete(chunkEvents.close)
|
||||
.then<ui.ImmutableBuffer>(ui.ImmutableBuffer.fromUint8List)
|
||||
.then<ui.Codec>(decode),
|
||||
chunkEvents: chunkEvents.stream,
|
||||
scale: 1.0,
|
||||
debugLabel: '"key"',
|
||||
informationCollector: () => <DiagnosticsNode>[
|
||||
DiagnosticsProperty<ImageProvider>('Image provider', this),
|
||||
DiagnosticsProperty<Uri>('URL', key),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '${objectRuntimeType(this, 'CustomNetworkImage')}("$url")';
|
||||
}
|
||||
|
||||
void main() => runApp(const ExampleApp());
|
||||
|
||||
class ExampleApp extends StatelessWidget {
|
||||
const ExampleApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return Image(
|
||||
image: const CustomNetworkImage('https://flutter.github.io/assets-for-api-docs/assets/widgets/flamingos.jpg'),
|
||||
width: constraints.hasBoundedWidth ? constraints.maxWidth : null,
|
||||
height: constraints.hasBoundedHeight ? constraints.maxHeight : null,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
// 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 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_api_samples/painting/image_provider/image_provider.0.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('$CustomNetworkImage', (WidgetTester tester) async {
|
||||
const String expectedUrl = 'https://flutter.github.io/assets-for-api-docs/assets/widgets/flamingos.jpg?dpr=3.0&locale=en-US&platform=android&width=800.0&height=600.0&bidi=ltr';
|
||||
final List<String> log = <String>[];
|
||||
final DebugPrintCallback originalDebugPrint = debugPrint;
|
||||
debugPrint = (String? message, {int? wrapWidth}) { log.add('$message'); };
|
||||
await tester.pumpWidget(const ExampleApp());
|
||||
expect(tester.takeException().toString(), 'Exception: Invalid image data');
|
||||
expect(log, <String>['Fetching "$expectedUrl"...']);
|
||||
debugPrint = originalDebugPrint;
|
||||
});
|
||||
}
|
@ -34,6 +34,7 @@ typedef DebugPrintCallback = void Function(String? message, { int? wrapWidth });
|
||||
/// See also:
|
||||
///
|
||||
/// * [DebugPrintCallback], for function parameters and usage details.
|
||||
/// * [debugPrintThrottled], the default implementation.
|
||||
DebugPrintCallback debugPrint = debugPrintThrottled;
|
||||
|
||||
/// Alternative implementation of [debugPrint] that does not throttle.
|
||||
@ -48,6 +49,8 @@ void debugPrintSynchronously(String? message, { int? wrapWidth }) {
|
||||
|
||||
/// Implementation of [debugPrint] that throttles messages. This avoids dropping
|
||||
/// messages on platforms that rate-limit their logging (for example, Android).
|
||||
///
|
||||
/// If `wrapWidth` is not null, the message is wrapped using [debugWordWrap].
|
||||
void debugPrintThrottled(String? message, { int? wrapWidth }) {
|
||||
final List<String> messageLines = message?.split('\n') ?? <String>['null'];
|
||||
if (wrapWidth != null) {
|
||||
@ -100,6 +103,9 @@ enum _WordWrapParseMode { inSpace, inWord, atBreak }
|
||||
|
||||
/// Wraps the given string at the given width.
|
||||
///
|
||||
/// The `message` should not contain newlines (`\n`, U+000A). Strings that may
|
||||
/// contain newlines should be [String.split] before being wrapped.
|
||||
///
|
||||
/// Wrapping occurs at space characters (U+0020). Lines that start with an
|
||||
/// octothorpe ("#", U+0023) are not wrapped (so for example, Dart stack traces
|
||||
/// won't be wrapped).
|
||||
|
@ -344,6 +344,16 @@ typedef ImageDecoderCallback = Future<ui.Codec> Function(
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// ## Creating an [ImageProvider]
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// In this example, a variant of [NetworkImage] is created that passes all the
|
||||
/// [ImageConfiguration] information (locale, platform, size, etc) to the server
|
||||
/// using query arguments in the image URL.
|
||||
///
|
||||
/// ** See code in examples/api/lib/painting/image_provider/image_provider.0.dart **
|
||||
/// {@end-tool}
|
||||
@optionalTypeArgs
|
||||
abstract class ImageProvider<T extends Object> {
|
||||
/// Abstract const constructor. This constructor enables subclasses to provide
|
||||
@ -596,7 +606,7 @@ abstract class ImageProvider<T extends Object> {
|
||||
return cache.evict(key);
|
||||
}
|
||||
|
||||
/// Converts an ImageProvider's settings plus an ImageConfiguration to a key
|
||||
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
|
||||
/// that describes the precise image to load.
|
||||
///
|
||||
/// The type of the key is determined by the subclass. It is a value that
|
||||
@ -605,6 +615,10 @@ abstract class ImageProvider<T extends Object> {
|
||||
/// arguments and [ImageConfiguration] objects should return keys that are
|
||||
/// '==' to each other (possibly by using a class for the key that itself
|
||||
/// implements [==]).
|
||||
///
|
||||
/// If the result can be determined synchronously, this function should return
|
||||
/// a [SynchronousFuture]. This allows image resolution to progress
|
||||
/// synchronously during a frame rather than delaying image loading.
|
||||
Future<T> obtainKey(ImageConfiguration configuration);
|
||||
|
||||
/// Converts a key into an [ImageStreamCompleter], and begins fetching the
|
||||
@ -632,10 +646,7 @@ abstract class ImageProvider<T extends Object> {
|
||||
/// Converts a key into an [ImageStreamCompleter], and begins fetching the
|
||||
/// image.
|
||||
///
|
||||
/// For backwards-compatibility the default implementation of this method returns
|
||||
/// an object that will cause [resolveStreamForKey] to consult [load]. However,
|
||||
/// implementors of this interface should only override this method and not
|
||||
/// [load], which is deprecated.
|
||||
/// This method is deprecated. Implement [loadImage] instead.
|
||||
///
|
||||
/// The [decode] callback provides the logic to obtain the codec for the
|
||||
/// image.
|
||||
@ -1477,6 +1488,8 @@ class ResizeImage extends ImageProvider<ResizeImageKey> {
|
||||
/// See also:
|
||||
///
|
||||
/// * [Image.network] for a shorthand of an [Image] widget backed by [NetworkImage].
|
||||
/// * The example at [ImageProvider], which shows a custom variant of this class
|
||||
/// that applies different logic for fetching the image.
|
||||
// TODO(ianh): Find some way to honor cache headers to the extent that when the
|
||||
// last reference to an image is released, we proactively evict the image from
|
||||
// our cache if the headers describe the image as having expired at that point.
|
||||
@ -1494,7 +1507,7 @@ abstract class NetworkImage extends ImageProvider<NetworkImage> {
|
||||
|
||||
/// The HTTP headers that will be used with [HttpClient.get] to fetch image from network.
|
||||
///
|
||||
/// When running flutter on the web, headers are not used.
|
||||
/// When running Flutter on the web, headers are not used.
|
||||
Map<String, String>? get headers;
|
||||
|
||||
@override
|
||||
|
@ -309,6 +309,16 @@ typedef ImageErrorWidgetBuilder = Widget Function(
|
||||
/// using the HTML renderer, the web engine delegates image decoding of network
|
||||
/// images to the Web, which does not support custom decode sizes.
|
||||
///
|
||||
/// ## Custom image providers
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// In this example, a variant of [NetworkImage] is created that passes all the
|
||||
/// [ImageConfiguration] information (locale, platform, size, etc) to the server
|
||||
/// using query arguments in the image URL.
|
||||
///
|
||||
/// ** See code in examples/api/lib/painting/image_provider/image_provider.0.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Icon], which shows an image from a font.
|
||||
@ -819,7 +829,7 @@ class Image extends StatefulWidget {
|
||||
/// {@end-tool}
|
||||
final ImageErrorWidgetBuilder? errorBuilder;
|
||||
|
||||
/// If non-null, require the image to have this width.
|
||||
/// If non-null, require the image to have this width (in logical pixels).
|
||||
///
|
||||
/// If null, the image will pick a size that best preserves its intrinsic
|
||||
/// aspect ratio.
|
||||
@ -831,7 +841,7 @@ class Image extends StatefulWidget {
|
||||
/// and height if the exact image dimensions are not known in advance.
|
||||
final double? width;
|
||||
|
||||
/// If non-null, require the image to have this height.
|
||||
/// If non-null, require the image to have this height (in logical pixels).
|
||||
///
|
||||
/// If null, the image will pick a size that best preserves its intrinsic
|
||||
/// aspect ratio.
|
||||
|
@ -71,17 +71,21 @@ void mockFlutterAssets() {
|
||||
class _MockHttpOverrides extends HttpOverrides {
|
||||
bool warningPrinted = false;
|
||||
@override
|
||||
HttpClient createHttpClient(SecurityContext? _) {
|
||||
HttpClient createHttpClient(SecurityContext? context) {
|
||||
if (!warningPrinted) {
|
||||
test_package.printOnFailure(
|
||||
'Warning: At least one test in this suite creates an HttpClient. When\n'
|
||||
'running a test suite that uses TestWidgetsFlutterBinding, all HTTP\n'
|
||||
'requests will return status code 400, and no network request will\n'
|
||||
'actually be made. Any test expecting a real network connection and\n'
|
||||
'Warning: At least one test in this suite creates an HttpClient. When '
|
||||
'running a test suite that uses TestWidgetsFlutterBinding, all HTTP '
|
||||
'requests will return status code 400, and no network request will '
|
||||
'actually be made. Any test expecting a real network connection and '
|
||||
'status code will fail.\n'
|
||||
'To test code that needs an HttpClient, provide your own HttpClient\n'
|
||||
'implementation to the code under test, so that your test can\n'
|
||||
'consistently provide a testable response to the code under test.');
|
||||
'To test code that needs an HttpClient, provide your own HttpClient '
|
||||
'implementation to the code under test, so that your test can '
|
||||
'consistently provide a testable response to the code under test.'
|
||||
.split('\n')
|
||||
.expand<String>((String line) => debugWordWrap(line, FlutterError.wrapWidth))
|
||||
.join('\n'),
|
||||
);
|
||||
warningPrinted = true;
|
||||
}
|
||||
return _MockHttpClient();
|
||||
|
Loading…
x
Reference in New Issue
Block a user