From d844a1dbff3a73a21d616f4af8d21fdd2bb5f61d Mon Sep 17 00:00:00 2001 From: amirh Date: Thu, 9 Nov 2017 09:56:36 -0800 Subject: [PATCH] Make the public ui.Codec API Future based instead of callback based. (flutter/engine#4341) --- engine/src/flutter/lib/ui/painting.dart | 68 ++++++++++++++++--- .../src/flutter/testing/dart/codec_test.dart | 52 +++++--------- 2 files changed, 75 insertions(+), 45 deletions(-) diff --git a/engine/src/flutter/lib/ui/painting.dart b/engine/src/flutter/lib/ui/painting.dart index 3841fe9d5f..6dc20808af 100644 --- a/engine/src/flutter/lib/ui/painting.dart +++ b/engine/src/flutter/lib/ui/painting.dart @@ -879,9 +879,6 @@ abstract class FrameInfo extends NativeFieldWrapperClass2 { Image get image native "FrameInfo_image"; } -/// Callback signature for [Codec.getNextFrame]. -typedef void NextFrameInfoCallback(FrameInfo frameInfo); - /// A handle to an image codec. abstract class Codec extends NativeFieldWrapperClass2 { /// Number of frames in this image. @@ -897,24 +894,36 @@ abstract class Codec extends NativeFieldWrapperClass2 { /// /// Wraps back to the first frame after returning the last frame. /// + /// The returned future can complete with an error if the decoding has failed. + Future getNextFrame() { + return _futurize(_getNextFrame); + } + /// Returns an error message on failure, null on success. - String getNextFrame(NextFrameInfoCallback callback) native "Codec_getNextFrame"; + String _getNextFrame(_Callback callback) native "Codec_getNextFrame"; /// Release the resources used by this object. The object is no longer usable /// after this method is called. void dispose() native "Codec_dispose"; } -/// Callback signature for [imageCodecFromList]. +/// Instantiates an image codec [Codec] object. /// -/// If parsing the data passed to [imageCodecFromList] failed, the result will -/// be null. -typedef void CodecCallback(Codec result); +/// [list] is the binary image data (e.g a PNG or GIF binary data). +/// The data can be for either static or animated images. +/// +/// The returned future can complete with an error if the image decoding has +/// failed. +Future instantiateImageCodec(Uint8List list) { + return _futurize( + (_Callback callback) => _instantiateImageCodec(list, callback) + ); +} -/// Instatiates a [Codec] object for an image binary data. +/// Instantiates a [Codec] object for an image binary data. /// /// Returns an error message if the instantiation has failed, null otherwise. -String instantiateImageCodec(Uint8List list, CodecCallback callback) +String _instantiateImageCodec(Uint8List list, _Callback callback) native "instantiateImageCodec"; /// Convert an image file from a byte array into an [Image] object. @@ -2433,3 +2442,42 @@ class PictureRecorder extends NativeFieldWrapperClass2 { /// Returns null if the PictureRecorder is not associated with a canvas. Picture endRecording() native "PictureRecorder_endRecording"; } + +/// Generic callback signature, used by [_futurize]. +typedef void _Callback(T result); + +/// Signature for a method that receives a [_Callback]. +/// +/// Return value should be null on success, and a string error message on +/// failure. +typedef String _Callbacker(_Callback callback); + +/// Converts a method that receives a value-returning callback to a method that +/// returns a Future. +/// +/// Example usage: +/// ```dart +/// typedef void IntCallback(int result); +/// +/// void doSomethingAndCallback(IntCallback callback) { +/// new Timer(new Duration(seconds: 1), () { callback(1); }); +/// } +/// +/// Future doSomething() { +/// return _futurize(domeSomethingAndCallback); +/// } +/// ``` +/// +Future _futurize(_Callbacker callbacker) async { + Completer completer = new Completer(); + String err = callbacker(completer.complete); + if (err != null) { + throw new Exception(err); + } + T result = await completer.future; + if (result == null) { + throw new Exception('operation failed'); + } + return result; +} + diff --git a/engine/src/flutter/testing/dart/codec_test.dart b/engine/src/flutter/testing/dart/codec_test.dart index b65a7bb681..0e3bf92d6b 100644 --- a/engine/src/flutter/testing/dart/codec_test.dart +++ b/engine/src/flutter/testing/dart/codec_test.dart @@ -14,52 +14,32 @@ void main() { test('Animation metadata', () async { Uint8List data = await _getSkiaResource('alphabetAnim.gif').readAsBytes(); - Completer completer = new Completer(); - expect(ui.instantiateImageCodec(data, completer.complete), null); - ui.Codec codec = await completer.future; + ui.Codec codec = await ui.instantiateImageCodec(data); + expect(codec, isNotNull); expect(codec.frameCount, 13); expect(codec.repetitionCount, 0); codec.dispose(); data = await _getSkiaResource('test640x479.gif').readAsBytes(); - completer = new Completer(); - expect(ui.instantiateImageCodec(data, completer.complete), null); - codec = await completer.future; + codec = await ui.instantiateImageCodec(data); expect(codec.frameCount, 4); expect(codec.repetitionCount, -1); }); - test('Fails when no callback provided', () async { - Uint8List data = await _getSkiaResource('alphabetAnim.gif').readAsBytes(); - expect(ui.instantiateImageCodec(data, null), 'Callback must be a function'); - }); - test('Fails with invalid data', () async { Uint8List data = new Uint8List.fromList([1, 2, 3]); - Completer completer = new Completer(); - expect(ui.instantiateImageCodec(data, completer.complete), null); - ui.Codec codec = await completer.future; - expect(codec, null); - }); - - test('nextFrame fails when no callback provided', () async { - Uint8List data = await _getSkiaResource('alphabetAnim.gif').readAsBytes(); - Completer completer = new Completer(); - expect(ui.instantiateImageCodec(data, completer.complete), null); - ui.Codec codec = await completer.future; - expect(codec.getNextFrame(null), 'Callback must be a function'); + expect( + ui.instantiateImageCodec(data), + throwsA(exceptionWithMessage('operation failed')) + ); }); test('nextFrame', () async { Uint8List data = await _getSkiaResource('test640x479.gif').readAsBytes(); - Completer completer = new Completer(); - expect(ui.instantiateImageCodec(data, completer.complete), null); - ui.Codec codec = await completer.future; + ui.Codec codec = await ui.instantiateImageCodec(data); List> decodedFrameInfos = []; for (int i = 0; i < 5; i++) { - Completer frameCompleter = new Completer(); - codec.getNextFrame(frameCompleter.complete); - ui.FrameInfo frameInfo = await frameCompleter.future; + ui.FrameInfo frameInfo = await codec.getNextFrame(); decodedFrameInfos.add([ frameInfo.durationMillis, frameInfo.image.width, @@ -77,14 +57,10 @@ void main() { test('non animated image', () async { Uint8List data = await _getSkiaResource('baby_tux.png').readAsBytes(); - Completer completer = new Completer(); - expect(ui.instantiateImageCodec(data, completer.complete), null); - ui.Codec codec = await completer.future; + ui.Codec codec = await ui.instantiateImageCodec(data); List> decodedFrameInfos = []; for (int i = 0; i < 2; i++) { - Completer frameCompleter = new Completer(); - codec.getNextFrame(frameCompleter.complete); - ui.FrameInfo frameInfo = await frameCompleter.future; + ui.FrameInfo frameInfo = await codec.getNextFrame(); decodedFrameInfos.add([ frameInfo.durationMillis, frameInfo.image.width, @@ -109,3 +85,9 @@ File _getSkiaResource(String fileName) { path.join('third_party', 'skia', 'resources', fileName); return new File(assetPath); } + +Matcher exceptionWithMessage(String m) { + return predicate((e) { + return e is Exception && e.message == m; + }); +}