@@ -9,6 +9,36 @@ import 'package:flutter/rendering.dart';
99import 'package:flutter/scheduler.dart' ;
1010import 'package:flutter/widgets.dart' ;
1111
12+ // A Future<ui.Image> that stores the resolved result.
13+ class _AsyncImage {
14+ _AsyncImage (Future <ui.Image > task) {
15+ _task = task.then ((ui.Image image) {
16+ _result = image;
17+ });
18+ }
19+
20+ // Returns the resolved image.
21+ Future <ui.Image > result () async {
22+ if (_result != null ) {
23+ return _result! ;
24+ }
25+ await _task;
26+ assert (_result != null );
27+ return _result! ;
28+ }
29+
30+ late final Future <void > _task;
31+ ui.Image ? _result;
32+
33+ // Wait for a list of `_AsyncImage` and returns the list of its resolved
34+ // images.
35+ static Future <List <ui.Image >> resolveList (List <_AsyncImage > targets) {
36+ final Iterable <Future <ui.Image >> images = targets.map< Future <ui.Image >> (
37+ (_AsyncImage target) => target.result ());
38+ return Future .wait< ui.Image > (images);
39+ }
40+ }
41+
1242/// Records the frames of an animating widget, and later displays the frames as a
1343/// grid in an animation sheet.
1444///
@@ -20,6 +50,7 @@ import 'package:flutter/widgets.dart';
2050/// Using this class includes the following steps:
2151///
2252/// * Create an instance of this class.
53+ /// * Register [dispose] to the test's tear down callbacks.
2354/// * Pump frames that render the target widget wrapped in [record] . Every frame
2455/// that has `recording` being true will be recorded.
2556/// * Acquire the output image with [collate] and compare against the golden
@@ -33,6 +64,7 @@ import 'package:flutter/widgets.dart';
3364/// testWidgets('Inkwell animation sheet', (WidgetTester tester) async {
3465/// // Create instance
3566/// final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(frameSize: const Size(48, 24));
67+ /// addTearDown(animationSheet.dispose);
3668///
3769/// final Widget target = Material(
3870/// child: Directionality(
@@ -90,6 +122,24 @@ class AnimationSheetBuilder {
90122 this .allLayers = false ,
91123 }) : assert (! kIsWeb);
92124
125+ /// Dispose all recorded frames and result images.
126+ ///
127+ /// This method must be called before the test case ends (usually as a tear
128+ /// down callback) to properly deallocate the images.
129+ ///
130+ /// After this method is called, there will be no frames to [collate] .
131+ Future <void > dispose () async {
132+ final List <_AsyncImage > targets = < _AsyncImage > [
133+ ..._recordedFrames,
134+ ..._results,
135+ ];
136+ _recordedFrames.clear ();
137+ _results.clear ();
138+ for (final ui.Image image in await _AsyncImage .resolveList (targets)) {
139+ image.dispose ();
140+ }
141+ }
142+
93143 /// The size of the child to be recorded.
94144 ///
95145 /// This size is applied as a tight layout constraint for the child, and is
@@ -112,20 +162,7 @@ class AnimationSheetBuilder {
112162 /// Defaults to false.
113163 final bool allLayers;
114164
115- final List <Future <ui.Image >> _recordedFrames = < Future <ui.Image >> [];
116- Future <List <ui.Image >> get _frames async {
117- final List <ui.Image > frames = await Future .wait< ui.Image > (_recordedFrames, eagerError: true );
118- assert (() {
119- for (final ui.Image frame in frames) {
120- assert (frame.width == frameSize.width && frame.height == frameSize.height,
121- 'Unexpected size mismatch: frame has (${frame .width }, ${frame .height }) '
122- 'while `frameSize` is $frameSize .'
123- );
124- }
125- return true ;
126- }());
127- return frames;
128- }
165+ final List <_AsyncImage > _recordedFrames = < _AsyncImage > [];
129166
130167 /// Returns a widget that renders a widget in a box that can be recorded.
131168 ///
@@ -152,22 +189,41 @@ class AnimationSheetBuilder {
152189 key: key,
153190 size: frameSize,
154191 allLayers: allLayers,
155- handleRecorded: recording ? _recordedFrames.add : null ,
192+ handleRecorded: ! recording ? null : (Future <ui.Image > futureImage) {
193+ _recordedFrames.add (_AsyncImage (() async {
194+ final ui.Image image = await futureImage;
195+ assert (image.width == frameSize.width && image.height == frameSize.height,
196+ 'Unexpected size mismatch: frame has (${image .width }, ${image .height }) '
197+ 'while `frameSize` is $frameSize .'
198+ );
199+ return image;
200+ }()));
201+ },
156202 child: child,
157203 );
158204 }
159205
206+ // The result images generated by `collate`.
207+ //
208+ // They're stored here to be disposed by [dispose].
209+ final List <_AsyncImage > _results = < _AsyncImage > [];
210+
160211 /// Returns an result image by putting all frames together in a table.
161212 ///
162- /// This method returns a table of captured frames, `cellsPerRow` images
163- /// per row, from left to right, top to bottom.
213+ /// This method returns an image that arranges the captured frames in a table,
214+ /// which has `cellsPerRow` images per row with the order from left to right,
215+ /// top to bottom.
216+ ///
217+ /// The result image of this method is managed by [AnimationSheetBuilder] ,
218+ /// and should not be disposed by the caller.
164219 ///
165220 /// An example of using this method can be found at [AnimationSheetBuilder] .
166221 Future <ui.Image > collate (int cellsPerRow) async {
167- final List <ui.Image > frames = await _frames;
168- assert (frames.isNotEmpty,
222+ assert (_recordedFrames.isNotEmpty,
169223 'No frames are collected. Have you forgot to set `recording` to true?' );
170- return _collateFrames (frames, frameSize, cellsPerRow);
224+ final _AsyncImage result = _AsyncImage (_collateFrames (_recordedFrames, frameSize, cellsPerRow));
225+ _results.add (result);
226+ return result.result ();
171227 }
172228}
173229
@@ -281,7 +337,8 @@ class _RenderPostFrameCallbacker extends RenderProxyBox {
281337 }
282338}
283339
284- Future <ui.Image > _collateFrames (List <ui.Image > frames, Size frameSize, int cellsPerRow) async {
340+ Future <ui.Image > _collateFrames (List <_AsyncImage > futureFrames, Size frameSize, int cellsPerRow) async {
341+ final List <ui.Image > frames = await _AsyncImage .resolveList (futureFrames);
285342 final int rowNum = (frames.length / cellsPerRow).ceil ();
286343
287344 final ui.PictureRecorder recorder = ui.PictureRecorder ();
0 commit comments