Unverified Commit cba41ca2 authored by Todd Volkert's avatar Todd Volkert Committed by GitHub

Remove assert from Image._handleImageFrame() (#33602)

Tickers being disabled and re-enabled can cause the
condition of a synchronous notification happening after
image frames have been delivered, which is valid in that
case. As such, this removes the assert.

https://github.com/flutter/flutter/issues/32374
parent 170309d6
...@@ -936,7 +936,6 @@ class _ImageState extends State<Image> with WidgetsBindingObserver { ...@@ -936,7 +936,6 @@ class _ImageState extends State<Image> with WidgetsBindingObserver {
} }
void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) { void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) {
assert(_frameNumber == null || !synchronousCall);
setState(() { setState(() {
_imageInfo = imageInfo; _imageInfo = imageInfo;
_loadingProgress = null; _loadingProgress = null;
......
...@@ -844,12 +844,12 @@ void main() { ...@@ -844,12 +844,12 @@ void main() {
expect(lastFrame, isNull); expect(lastFrame, isNull);
expect(find.byType(Center), findsOneWidget); expect(find.byType(Center), findsOneWidget);
expect(find.byType(RawImage), findsOneWidget); expect(find.byType(RawImage), findsOneWidget);
streamCompleter.notifyListeners(imageInfo: ImageInfo(image: await nextFrame())); streamCompleter.setData(imageInfo: ImageInfo(image: await nextFrame()));
await tester.pump(); await tester.pump();
expect(lastFrame, 0); expect(lastFrame, 0);
expect(find.byType(Center), findsOneWidget); expect(find.byType(Center), findsOneWidget);
expect(find.byType(RawImage), findsOneWidget); expect(find.byType(RawImage), findsOneWidget);
streamCompleter.notifyListeners(imageInfo: ImageInfo(image: await nextFrame())); streamCompleter.setData(imageInfo: ImageInfo(image: await nextFrame()));
await tester.pump(); await tester.pump();
expect(lastFrame, 1); expect(lastFrame, 1);
expect(find.byType(Center), findsOneWidget); expect(find.byType(Center), findsOneWidget);
...@@ -877,7 +877,7 @@ void main() { ...@@ -877,7 +877,7 @@ void main() {
expect(lastFrame, isNull); expect(lastFrame, isNull);
expect(lastFrameWasSync, isFalse); expect(lastFrameWasSync, isFalse);
expect(find.byType(RawImage), findsOneWidget); expect(find.byType(RawImage), findsOneWidget);
streamCompleter.notifyListeners(imageInfo: ImageInfo(image: image)); streamCompleter.setData(imageInfo: ImageInfo(image: image));
await tester.pump(); await tester.pump();
expect(lastFrame, 0); expect(lastFrame, 0);
expect(lastFrameWasSync, isFalse); expect(lastFrameWasSync, isFalse);
...@@ -904,7 +904,7 @@ void main() { ...@@ -904,7 +904,7 @@ void main() {
expect(lastFrame, 0); expect(lastFrame, 0);
expect(lastFrameWasSync, isTrue); expect(lastFrameWasSync, isTrue);
expect(find.byType(RawImage), findsOneWidget); expect(find.byType(RawImage), findsOneWidget);
streamCompleter.notifyListeners(imageInfo: ImageInfo(image: image)); streamCompleter.setData(imageInfo: ImageInfo(image: image));
await tester.pump(); await tester.pump();
expect(lastFrame, 1); expect(lastFrame, 1);
expect(lastFrameWasSync, isTrue); expect(lastFrameWasSync, isTrue);
...@@ -942,6 +942,79 @@ void main() { ...@@ -942,6 +942,79 @@ void main() {
expect(tester.state(find.byType(Image)), same(state)); expect(tester.state(find.byType(Image)), same(state));
}); });
testWidgets('Image state handles enabling and disabling of tickers', (WidgetTester tester) async {
final ui.Codec codec = await tester.runAsync(() {
return ui.instantiateImageCodec(Uint8List.fromList(kAnimatedGif));
});
Future<ui.Image> nextFrame() async {
final ui.FrameInfo frameInfo = await tester.runAsync(codec.getNextFrame);
return frameInfo.image;
}
final TestImageStreamCompleter streamCompleter = TestImageStreamCompleter();
final TestImageProvider imageProvider = TestImageProvider(streamCompleter: streamCompleter);
int lastFrame;
int buildCount = 0;
Widget buildFrame(BuildContext context, Widget child, int frame, bool wasSynchronouslyLoaded) {
lastFrame = frame;
buildCount++;
return child;
}
await tester.pumpWidget(
TickerMode(
enabled: true,
child: Image(
image: imageProvider,
frameBuilder: buildFrame,
),
),
);
final State<Image> state = tester.state(find.byType(Image));
expect(lastFrame, isNull);
expect(buildCount, 1);
streamCompleter.setData(imageInfo: ImageInfo(image: await nextFrame()));
await tester.pump();
expect(lastFrame, 0);
expect(buildCount, 2);
await tester.pumpWidget(
TickerMode(
enabled: false,
child: Image(
image: imageProvider,
frameBuilder: buildFrame,
),
),
);
expect(tester.state(find.byType(Image)), same(state));
expect(lastFrame, 0);
expect(buildCount, 3);
streamCompleter.setData(imageInfo: ImageInfo(image: await nextFrame()));
streamCompleter.setData(imageInfo: ImageInfo(image: await nextFrame()));
await tester.pump();
expect(lastFrame, 0);
expect(buildCount, 3);
await tester.pumpWidget(
TickerMode(
enabled: true,
child: Image(
image: imageProvider,
frameBuilder: buildFrame,
),
),
);
expect(tester.state(find.byType(Image)), same(state));
expect(lastFrame, 1); // missed a frame because we weren't animating at the time
expect(buildCount, 4);
});
testWidgets('Image invokes loadingBuilder on chunk event notification', (WidgetTester tester) async { testWidgets('Image invokes loadingBuilder on chunk event notification', (WidgetTester tester) async {
final ui.Image image = await tester.runAsync(createTestImage); final ui.Image image = await tester.runAsync(createTestImage);
final TestImageStreamCompleter streamCompleter = TestImageStreamCompleter(); final TestImageStreamCompleter streamCompleter = TestImageStreamCompleter();
...@@ -966,19 +1039,19 @@ void main() { ...@@ -966,19 +1039,19 @@ void main() {
expect(chunkEvents.length, 1); expect(chunkEvents.length, 1);
expect(chunkEvents.first, isNull); expect(chunkEvents.first, isNull);
expect(tester.binding.hasScheduledFrame, isFalse); expect(tester.binding.hasScheduledFrame, isFalse);
streamCompleter.notifyListeners(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100)); streamCompleter.setData(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100));
expect(tester.binding.hasScheduledFrame, isTrue); expect(tester.binding.hasScheduledFrame, isTrue);
await tester.pump(); await tester.pump();
expect(chunkEvents.length, 2); expect(chunkEvents.length, 2);
expect(find.text('loading 10 / 100'), findsOneWidget); expect(find.text('loading 10 / 100'), findsOneWidget);
expect(find.byType(RawImage), findsNothing); expect(find.byType(RawImage), findsNothing);
streamCompleter.notifyListeners(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 30, expectedTotalBytes: 100)); streamCompleter.setData(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 30, expectedTotalBytes: 100));
expect(tester.binding.hasScheduledFrame, isTrue); expect(tester.binding.hasScheduledFrame, isTrue);
await tester.pump(); await tester.pump();
expect(chunkEvents.length, 3); expect(chunkEvents.length, 3);
expect(find.text('loading 30 / 100'), findsOneWidget); expect(find.text('loading 30 / 100'), findsOneWidget);
expect(find.byType(RawImage), findsNothing); expect(find.byType(RawImage), findsNothing);
streamCompleter.notifyListeners(imageInfo: ImageInfo(image: image)); streamCompleter.setData(imageInfo: ImageInfo(image: image));
await tester.pump(); await tester.pump();
expect(chunkEvents.length, 4); expect(chunkEvents.length, 4);
expect(find.byType(Text), findsNothing); expect(find.byType(Text), findsNothing);
...@@ -998,12 +1071,12 @@ void main() { ...@@ -998,12 +1071,12 @@ void main() {
); );
expect(tester.binding.hasScheduledFrame, isFalse); expect(tester.binding.hasScheduledFrame, isFalse);
streamCompleter.notifyListeners(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100)); streamCompleter.setData(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100));
expect(tester.binding.hasScheduledFrame, isFalse); expect(tester.binding.hasScheduledFrame, isFalse);
streamCompleter.notifyListeners(imageInfo: ImageInfo(image: image)); streamCompleter.setData(imageInfo: ImageInfo(image: image));
expect(tester.binding.hasScheduledFrame, isTrue); expect(tester.binding.hasScheduledFrame, isTrue);
await tester.pump(); await tester.pump();
streamCompleter.notifyListeners(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100)); streamCompleter.setData(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100));
expect(tester.binding.hasScheduledFrame, isFalse); expect(tester.binding.hasScheduledFrame, isFalse);
expect(find.byType(RawImage), findsOneWidget); expect(find.byType(RawImage), findsOneWidget);
}); });
...@@ -1029,7 +1102,7 @@ void main() { ...@@ -1029,7 +1102,7 @@ void main() {
expect(find.byType(Padding), findsOneWidget); expect(find.byType(Padding), findsOneWidget);
expect(find.byType(RawImage), findsOneWidget); expect(find.byType(RawImage), findsOneWidget);
expect(tester.widget<Padding>(find.byType(Padding)).child, isInstanceOf<RawImage>()); expect(tester.widget<Padding>(find.byType(Padding)).child, isInstanceOf<RawImage>());
streamCompleter.notifyListeners(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100)); streamCompleter.setData(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100));
await tester.pump(); await tester.pump();
expect(find.byType(Center), findsOneWidget); expect(find.byType(Center), findsOneWidget);
expect(find.byType(Padding), findsOneWidget); expect(find.byType(Padding), findsOneWidget);
...@@ -1047,7 +1120,7 @@ void main() { ...@@ -1047,7 +1120,7 @@ void main() {
); );
expect(find.byType(RawImage), findsOneWidget); expect(find.byType(RawImage), findsOneWidget);
streamCompleter.notifyListeners(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100)); streamCompleter.setData(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100));
expect(tester.binding.hasScheduledFrame, isFalse); expect(tester.binding.hasScheduledFrame, isFalse);
final State<Image> state = tester.state(find.byType(Image)); final State<Image> state = tester.state(find.byType(Image));
...@@ -1063,7 +1136,7 @@ void main() { ...@@ -1063,7 +1136,7 @@ void main() {
expect(find.byType(Center), findsOneWidget); expect(find.byType(Center), findsOneWidget);
expect(find.byType(RawImage), findsOneWidget); expect(find.byType(RawImage), findsOneWidget);
expect(tester.state(find.byType(Image)), same(state)); expect(tester.state(find.byType(Image)), same(state));
streamCompleter.notifyListeners(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100)); streamCompleter.setData(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100));
expect(tester.binding.hasScheduledFrame, isTrue); expect(tester.binding.hasScheduledFrame, isTrue);
await tester.pump(); await tester.pump();
expect(find.byType(Center), findsOneWidget); expect(find.byType(Center), findsOneWidget);
...@@ -1085,7 +1158,7 @@ void main() { ...@@ -1085,7 +1158,7 @@ void main() {
expect(find.byType(Center), findsOneWidget); expect(find.byType(Center), findsOneWidget);
expect(find.byType(RawImage), findsOneWidget); expect(find.byType(RawImage), findsOneWidget);
streamCompleter.notifyListeners(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100)); streamCompleter.setData(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100));
expect(tester.binding.hasScheduledFrame, isTrue); expect(tester.binding.hasScheduledFrame, isTrue);
await tester.pump(); await tester.pump();
expect(find.byType(Center), findsOneWidget); expect(find.byType(Center), findsOneWidget);
...@@ -1099,7 +1172,7 @@ void main() { ...@@ -1099,7 +1172,7 @@ void main() {
expect(find.byType(Center), findsNothing); expect(find.byType(Center), findsNothing);
expect(find.byType(RawImage), findsOneWidget); expect(find.byType(RawImage), findsOneWidget);
expect(tester.state(find.byType(Image)), same(state)); expect(tester.state(find.byType(Image)), same(state));
streamCompleter.notifyListeners(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100)); streamCompleter.setData(chunkEvent: const ImageChunkEvent(cumulativeBytesLoaded: 10, expectedTotalBytes: 100));
expect(tester.binding.hasScheduledFrame, isFalse); expect(tester.binding.hasScheduledFrame, isFalse);
}); });
} }
...@@ -1141,16 +1214,17 @@ class TestImageProvider extends ImageProvider<TestImageProvider> { ...@@ -1141,16 +1214,17 @@ class TestImageProvider extends ImageProvider<TestImageProvider> {
} }
class TestImageStreamCompleter extends ImageStreamCompleter { class TestImageStreamCompleter extends ImageStreamCompleter {
TestImageStreamCompleter([this.synchronousImage]); TestImageStreamCompleter([this._currentImage]);
final ImageInfo synchronousImage; ImageInfo _currentImage;
final Set<ImageStreamListener> listeners = <ImageStreamListener>{}; final Set<ImageStreamListener> listeners = <ImageStreamListener>{};
@override @override
void addListener(ImageStreamListener listener) { void addListener(ImageStreamListener listener) {
listeners.add(listener); listeners.add(listener);
if (synchronousImage != null) if (_currentImage != null) {
listener.onImage(synchronousImage, true); listener.onImage(_currentImage, true);
}
} }
@override @override
...@@ -1158,10 +1232,13 @@ class TestImageStreamCompleter extends ImageStreamCompleter { ...@@ -1158,10 +1232,13 @@ class TestImageStreamCompleter extends ImageStreamCompleter {
listeners.remove(listener); listeners.remove(listener);
} }
void notifyListeners({ void setData({
ImageInfo imageInfo, ImageInfo imageInfo,
ImageChunkEvent chunkEvent, ImageChunkEvent chunkEvent,
}) { }) {
if (imageInfo != null) {
_currentImage = imageInfo;
}
final List<ImageStreamListener> localListeners = listeners.toList(); final List<ImageStreamListener> localListeners = listeners.toList();
for (ImageStreamListener listener in localListeners) { for (ImageStreamListener listener in localListeners) {
if (imageInfo != null) { if (imageInfo != null) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment