diff --git a/README.md b/README.md index 367048a..85e91c6 100644 --- a/README.md +++ b/README.md @@ -49,18 +49,20 @@ void main() async { // You can run an application from the daemon. // Or alternatively you can attach using `daemon.attach` - final application = daemon.run( + final application = await daemon.run( arguments: [ // Any flutter arguments go here. ], workingDirectory: 'your/flutter/app/location/', ); + // Wait until it is fully started. + await application.started; application.events.listen((event) { // Listen to events specifically emitted by this application. }); - +g // Restart the application await application.restart(); diff --git a/example/main.dart b/example/main.dart index 3cf5ffb..502d051 100644 --- a/example/main.dart +++ b/example/main.dart @@ -10,12 +10,15 @@ void main(List arguments) async { final daemon = FlutterDaemon(); daemon.events.listen(print); - final workingDirectory = arguments.removeAt(0); + final workingDirectory = arguments.first; final application = await daemon.run( - arguments: arguments, + arguments: arguments.sublist(1), workingDirectory: workingDirectory, ); + // Wait for it to be fully started. + await application.started; + print('started'); await Future.delayed(const Duration(seconds: 10)); diff --git a/lib/src/flutter_application/flutter_application.dart b/lib/src/flutter_application/flutter_application.dart index cf568ef..8a1ee5d 100644 --- a/lib/src/flutter_application/flutter_application.dart +++ b/lib/src/flutter_application/flutter_application.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter_daemon/flutter_daemon.dart'; export 'requests/requests.dart'; @@ -10,7 +12,21 @@ export 'requests/requests.dart'; /// {@endtemplate} class FlutterApplication { /// {@macro flutter_application} - FlutterApplication(this.appId, this._daemon); + FlutterApplication(this.appId, this._daemon) : _started = Completer() { + events + .firstWhere((e) => e.event == 'app.started') + .whenComplete(_started.complete); + } + + /// {@macro flutter_application} + /// + /// Created when the daemon is getting attached to a running app. + FlutterApplication.attached( + this.appId, + this._daemon, + ) : _started = Completer() { + _started.complete(); + } /// The application id of the attached flutter app. final String appId; @@ -19,6 +35,13 @@ class FlutterApplication { late final Stream events = _daemon.events.where((event) => event.params['appId'] == appId); + /// Resolves once the application has emitted the `app.started` event. + /// + /// If this application was created through an attach it will already be + /// resolved. + Future get started => _started.future; + final Completer _started; + final FlutterDaemon _daemon; /// Restarts the application. diff --git a/lib/src/flutter_daemon/flutter_daemon.dart b/lib/src/flutter_daemon/flutter_daemon.dart index 98450b0..899ba84 100644 --- a/lib/src/flutter_daemon/flutter_daemon.dart +++ b/lib/src/flutter_daemon/flutter_daemon.dart @@ -144,8 +144,8 @@ class FlutterDaemon { if (data.containsKey('event')) { final event = FlutterDaemonEvent.fromJSON(data); _eventsController.add(event); - if (event.event == 'app.started') { - log('App started', level: 300); + if (event.event == 'app.start') { + log('App is attachable', level: 300); completer.complete( FlutterApplication(event.params['appId'] as String, this), ); diff --git a/test/src/flutter_application/flutter_application_test.dart b/test/src/flutter_application/flutter_application_test.dart index 25fbdd9..9138b54 100644 --- a/test/src/flutter_application/flutter_application_test.dart +++ b/test/src/flutter_application/flutter_application_test.dart @@ -45,6 +45,16 @@ void main() { }); }); + // Ensure the Flutter application get's an app.started event. + when(() => daemon.events).thenAnswer((_) { + return Stream.value( + FlutterDaemonEvent.fromJSON({ + 'event': 'app.started', + 'params': {'appId': 'appId'}, + }), + ); + }); + application = FlutterApplication('appId', daemon); }); @@ -55,6 +65,11 @@ void main() { registerFallbackValue(AppCallServiceExtensionRequest('dummy', 'dummy')); }); + test('$FlutterApplication.attached resolves started directly', () { + final app = FlutterApplication.attached('appId', daemon); + expect(app.started, completes); + }); + test('restart', () async { final response = await application.restart(); expect(response.code, equals(0)); diff --git a/test/src/flutter_daemon/flutter_daemon_test.dart b/test/src/flutter_daemon/flutter_daemon_test.dart index 283169d..b79a216 100644 --- a/test/src/flutter_daemon/flutter_daemon_test.dart +++ b/test/src/flutter_daemon/flutter_daemon_test.dart @@ -67,8 +67,14 @@ void main() { /// Emit app start event. await stdout.appStart(); + + final app = await appFuture; expect(await appFuture, isA()); + // Emit app started event; + await stdout.appStarted(); + expect(app.started, completes); + expect(daemon.isFinished, isFalse); exitWith(0); await finishedFuture; @@ -81,8 +87,14 @@ void main() { /// Emit app start event. await stdout.appStart(); + + final app = await appFuture; expect(await appFuture, isA()); + // Emit app started event; + await stdout.appStarted(); + expect(app.started, completes); + expect(daemon.isFinished, isFalse); exitWith(0); await finishedFuture; @@ -94,8 +106,14 @@ void main() { /// Emit app start event. await stdout.appStart(); + + final app = await appFuture; expect(await appFuture, isA()); + // Emit app started event; + await stdout.appStarted(); + expect(app.started, completes); + expect( daemon.run(arguments: [], workingDirectory: ''), throwsA( @@ -123,8 +141,14 @@ void main() { /// Emit app start event. await stdout.appStart(); + + final app = await appFuture; expect(await appFuture, isA()); + // Emit app started event; + await stdout.appStarted(); + expect(app.started, completes); + exitWith(0); await daemon.dispose(); @@ -165,8 +189,14 @@ void main() { /// Emit app start event. await stdout.appStart(); + + final app = await appFuture; expect(await appFuture, isA()); + // Emit app started event; + await stdout.appStarted(); + expect(app.started, completes); + when(() => stdin.writeln(any())).thenAnswer((_) { final requests = json.decode( _.positionalArguments.first as String, @@ -201,8 +231,14 @@ void main() { /// Emit app start event. await stdout.appStart(); + + final app = await appFuture; expect(await appFuture, isA()); + // Emit app started event; + await stdout.appStarted(); + expect(app.started, completes); + final eventFuture = daemon.events.first; stdout.write( json.encode([ @@ -223,8 +259,14 @@ void main() { /// Emit app start event. await stdout.appStart(); + + final app = await appFuture; expect(await appFuture, isA()); + // Emit app started event; + await stdout.appStarted(); + expect(app.started, completes); + final eventFuture = daemon.events.first; // Thanks to Flutter we can now get some errors on stdout instead of @@ -253,6 +295,18 @@ extension on StreamController> { void write(String data) => add(utf8.encode('$data\n')); Future appStart() { + write( + json.encode([ + { + 'event': 'app.start', + 'params': {'appId': '0000'}, + } + ]), + ); + return Future.delayed(Duration.zero); + } + + Future appStarted() { write( json.encode([ {