-
Notifications
You must be signed in to change notification settings - Fork 469
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FlareControls never initializes in unit test & FlareActor never displays #160
Comments
Ah, I found the circumstances under which the completer completes before the test ends - if you pump after the golden test, then the initialize call is finally provoked. But as you can see in the given example, 200 pumps, 100 of them advancing time, before the golden test, avail nothing. |
/cc @luigi-rosso |
Looks like this is related to flutter/flutter#24703 We use compute to perform the loading of a Flare file. Compute futures never complete in the testing environment. I'm looking into implementing a workaround using kDebugMode. |
Try updating to Then call import 'dart:async';
import 'package:flare_flutter/flare_actor.dart';
import 'package:flare_flutter/flare_testing.dart';
import 'package:flare_flutter/flare_controls.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
class DemoControls extends FlareControls {
Completer completer;
DemoControls(this.completer);
@override
void initialize(artboard) {
super.initialize(artboard);
print('initialized');
completer.complete();
}
}
void main() {
FlareTesting.setup();
Completer completer;
setUp(() {
completer = Completer();
});
testWidgets('Flare initialize failure', (tester) async {
await tester.pumpWidget(MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.transparent,
body: Container(
width: 500,
height: 500,
child: FlareActor(
'assets/Filip.flr',
alignment: Alignment.center,
fit: BoxFit.contain,
controller: DemoControls(completer),
),
),
),
));
await tester.pumpAndSettle();
/// This actually works in so far as it makes it outputs the print
/// statement, but the rest of the test oddly locks up.
// await completer.future;
// print("awaited completer...");
// This is blank, of course, because the actor is not initialized.
await expectLater(
find.byType(Scaffold).first, matchesGoldenFile('some_golden_file.png'));
print('golden-checked');
print('done');
});
}
Note that I also had to remove |
Thanks, that helped significantly.
The animation has not advanced, whereas
The animation has moved on as expected, even though in both cases it should be 400ms having passed. It seems like the animation controller's timer doesn't initialize until the first actual time increment, so the first frame worth of time passing is lost. |
I suspect this has to do with the load process being asynchronous. The controller only initializes once the file is loaded. Your strategy with the completer seems like the best way to do it: wait for the initialize and then pump 400 milliseconds. I couldn't diagnose why simply awaiting the completer would cause subsequent code to lockup. I'll do some further testing to see if I can advance an animation a specific amount of time via the testing framework. |
Your observations were correct, my assumption was wrong. The first pump effectively starts the rendering/animation process. This is due to how Flare schedules repainting and tracks elapsed time. This code and comments should help clarify: import 'dart:async';
import 'package:flare_flutter/flare.dart';
import 'package:flare_flutter/flare_actor.dart';
import 'package:flare_flutter/flare_testing.dart';
import 'package:flare_flutter/flare_controls.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
class DemoControls extends FlareControls {
Completer completer;
DemoControls(this.completer);
double _elapsedTime = 0.0;
double get elapsedTime => _elapsedTime;
@override
void initialize(artboard) {
super.initialize(artboard);
print('initialized');
completer.complete();
}
@override
bool advance(FlutterActorArtboard artboard, double elapsed) {
print("DemoControls advancing $elapsed seconds");
_elapsedTime += elapsed;
super.advance(artboard, elapsed);
return true;
}
}
void main() {
FlareTesting.setup();
Completer completer;
DemoControls controls;
setUp(() {
completer = Completer();
controls = DemoControls(completer);
});
testWidgets('Flare initialize failure', (tester) async {
await tester.pumpWidget(MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.transparent,
body: Container(
width: 500,
height: 500,
child: FlareActor(
'assets/Filip.flr',
alignment: Alignment.center,
fit: BoxFit.contain,
controller: controls,
),
),
),
));
await tester.pump();
// Wait for initialize. Doesn't make a
// difference, you can comment out this
// whole block and have the same results.
print("Wait for completer");
await tester.runAsync(() async {
await completer.future;
});
print("First pump.");
// No matter how long this first pump is,
// the controller advances 0 seconds.
// This is because of how Flare repaints and
// measures time:
// Scheduling a frame callback and then
// computing elapsed time between previous
// paint. So the first time it paints, it needs
// to mark the first timestamp. So the first
// elapsed value when starting/re-starting
// playback is always 0.
await tester.pump(Duration(milliseconds: 1));
print("Second pump.");
// Now that _lastFrameTime in FlareRenderBox is
// set, time will elapse as expected.
await tester.pump(Duration(milliseconds: 200));
print("Controller elapsed: ${controls.elapsedTime}");
// This is blank, of course, because the actor is not initialized.
await expectLater(
find.byType(Scaffold).first, matchesGoldenFile('some_golden_file.png'));
print('golden-checked');
print('done');
});
} Output should look similar to:
Open to suggestions for how to improve this! We originally just used the frame scheduler, and let it schedule the next frame if necessary, but this made it necessary to carefully track when a widget was removed from the hierarchy and sometimes these calls would come in rapid succession when navigating (detach/re-attach) which would then need debouncing. The approach we settled on was to use the scheduler in response to a paint, as paint only happens when the widget is mounted. |
I think if, in flare_render_box.dart, you made _lastFrameTime null for "hasn't been set yet" rather than using 0 for unset, then pump(no duration) would update it to zero, and pump(some duration) after that would then advance it by that duration, bringing its behavior in line with that of other animations. Edit: |
We've found that by running test and wrapping them in runUnsynchronized we can keep flare animations working without any obvious issues
This basically means the find won't wait until there's no frame left to render before trying to find the object and therefore it doesn't timeout when it's busy always running a rive animation (or any other type of animation) |
I think that's a solution to a different problem. Good to know though! |
It looks like the zero-duration pump not starting the animation has been fixed - I suspect the timer behavior has been changed significantly since another timer-related issue was also fixed. |
No, my mistake, the animation not starting until after the first time-advance is still an issue, I just happened to be touching it in a test where the animation was completing a full cycle. But I'll open it as a separate issue, because the title of this one has nothing to do with it. |
Created issue #262 for this. |
The text was updated successfully, but these errors were encountered: