Add FlutterView and ViewManager and hook them into the ResidentRunner. (#5345)
- [x] Refactor view support into separate classes.
- [x] Make the hot runner grab the main view and call runFromSource on it.
- [x] Remove Device.needsDevFS (because it is always true).
diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart
index b254568..36b374a 100644
--- a/packages/flutter_tools/lib/src/devfs.dart
+++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -365,8 +365,12 @@
if (_dirtyEntries.length > 0) {
status = logger.startProgress('Updating files...');
if (_httpWriter != null) {
- await _httpWriter.write(_dirtyEntries,
- progressReporter: progressReporter);
+ try {
+ await _httpWriter.write(_dirtyEntries,
+ progressReporter: progressReporter);
+ } catch (e) {
+ printError("Could not update files on device: $e");
+ }
} else {
// Make service protocol requests for each.
for (DevFSEntry entry in _dirtyEntries) {
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index d343e5f..ed53c6e 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -192,9 +192,6 @@
/// Does this device implement support for hot reloading / restarting?
bool get supportsHotMode => false;
- /// Does this device need a DevFS to support hot mode?
- bool get needsDevFS => true;
-
/// Run from a file. Necessary for hot mode.
Future<bool> runFromFile(ApplicationPackage package,
String scriptUri,
diff --git a/packages/flutter_tools/lib/src/hot.dart b/packages/flutter_tools/lib/src/hot.dart
index 2b1b2d9..0fb35c2 100644
--- a/packages/flutter_tools/lib/src/hot.dart
+++ b/packages/flutter_tools/lib/src/hot.dart
@@ -23,6 +23,7 @@
import 'observatory.dart';
import 'resident_runner.dart';
import 'toolchain.dart';
+import 'view.dart';
String getDevFSLoaderScript() {
return path.absolute(path.join(Cache.flutterRoot,
@@ -238,17 +239,13 @@
await startEchoingDeviceLog();
- if (device.needsDevFS) {
- printStatus('Launching loader on ${device.name}...');
- } else {
- printStatus('Launching ${getDisplayPath(_mainPath)} on ${device.name}...');
- }
+ printStatus('Launching loader on ${device.name}...');
// Start the loader.
Future<LaunchResult> futureResult = device.startApp(
_package,
debuggingOptions.buildMode,
- mainPath: device.needsDevFS ? getDevFSLoaderScript() : _mainPath,
+ mainPath: getDevFSLoaderScript(),
debuggingOptions: debuggingOptions,
platformArgs: platformArgs,
route: route
@@ -266,48 +263,43 @@
LaunchResult result = await futureResult;
if (!result.started) {
- if (device.needsDevFS) {
- printError('Error launching DevFS loader on ${device.name}.');
- } else {
- printError('Error launching ${getDisplayPath(_mainPath)} on ${device.name}.');
- }
+ printError('Error launching DevFS loader on ${device.name}.');
await stopEchoingDeviceLog();
return 2;
}
await connectToServiceProtocol(result.observatoryPort);
- if (device.needsDevFS) {
- try {
- Uri baseUri = await _initDevFS();
- if (connectionInfoCompleter != null) {
- connectionInfoCompleter.complete(
- new DebugConnectionInfo(result.observatoryPort, baseUri: baseUri.toString())
- );
- }
- } catch (error) {
- printError('Error initializing DevFS: $error');
- return 3;
+ try {
+ Uri baseUri = await _initDevFS();
+ if (connectionInfoCompleter != null) {
+ connectionInfoCompleter.complete(
+ new DebugConnectionInfo(result.observatoryPort, baseUri: baseUri.toString())
+ );
}
- _loaderShowMessage('Connecting...', progress: 0);
- bool devfsResult = await _updateDevFS(
- progressReporter: (int progress, int max) {
- if (progress % 10 == 0)
- _loaderShowMessage('Syncing files to device...', progress: progress, max: max);
- }
- );
- if (!devfsResult) {
- _loaderShowMessage('Failed.');
- printError('Could not perform initial file synchronization.');
- return 3;
- }
- printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...');
- _loaderShowMessage('Launching...');
- await _launchFromDevFS(_package, _mainPath);
- } else {
- if (connectionInfoCompleter != null)
- connectionInfoCompleter.complete(new DebugConnectionInfo(result.observatoryPort));
+ } catch (error) {
+ printError('Error initializing DevFS: $error');
+ return 3;
}
+ _loaderShowMessage('Connecting...', progress: 0);
+ bool devfsResult = await _updateDevFS(
+ progressReporter: (int progress, int max) {
+ if (progress % 10 == 0)
+ _loaderShowMessage('Syncing files to device...', progress: progress, max: max);
+ }
+ );
+ if (!devfsResult) {
+ _loaderShowMessage('Failed.');
+ printError('Could not perform initial file synchronization.');
+ return 3;
+ }
+
+ await viewManager.refresh();
+ printStatus('Connected to view: ${viewManager.mainView}');
+
+ printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...');
+ _loaderShowMessage('Launching...');
+ await _launchFromDevFS(_package, _mainPath);
_startReadingFromControlPipe();
@@ -383,9 +375,6 @@
}
Future<Null> _evictDirtyAssets() async {
- if (_devFS == null) {
- return;
- }
if (_devFS.dirtyAssetEntries.length == 0) {
return;
}
@@ -408,26 +397,8 @@
Future<Null> _launchInView(String entryPath,
String packagesPath,
String assetsDirectoryPath) async {
- String viewId = await serviceProtocol.getFirstViewId();
- // When this completer completes the isolate is running.
- // TODO(johnmccutchan): Have the framework send an event after the first
- // frame is rendered and use that instead of 'runnable'.
- Completer<Null> completer = new Completer<Null>();
- StreamSubscription<Event> subscription =
- serviceProtocol.onIsolateEvent.listen((Event event) {
- if (event.kind == 'IsolateStart') {
- printTrace('Isolate is spawned.');
- } else if (event.kind == 'IsolateRunnable') {
- printTrace('Isolate is runnable.');
- completer.complete(null);
- }
- });
- await serviceProtocol.runInView(viewId,
- entryPath,
- packagesPath,
- assetsDirectoryPath);
- await completer.future;
- await subscription.cancel();
+ FlutterView view = viewManager.mainView;
+ return view.runFromSource(entryPath, packagesPath, assetsDirectoryPath);
}
Future<Null> _launchFromDevFS(ApplicationPackage package,
@@ -444,27 +415,11 @@
deviceAssetsDirectoryPath);
}
- Future<Null> _launchFromDisk(ApplicationPackage package,
- String mainScript) async {
- Uri baseUri = new Uri.directory(_projectRootPath);
- String entryPath = path.relative(mainScript, from: _projectRootPath);
- String diskEntryPath = baseUri.resolve(entryPath).toFilePath();
- String diskPackagesPath = baseUri.resolve('.packages').toFilePath();
- String diskAssetsDirectoryPath = baseUri.resolve('build/flx').toFilePath();
- await _launchInView(diskEntryPath,
- diskPackagesPath,
- diskAssetsDirectoryPath);
- }
-
Future<Null> _restartFromSources() async {
FirstFrameTimer firstFrameTimer = new FirstFrameTimer(serviceProtocol);
firstFrameTimer.start();
- if (_devFS == null) {
- await _launchFromDisk(_package, _mainPath);
- } else {
- await _updateDevFS();
- await _launchFromDevFS(_package, _mainPath);
- }
+ await _updateDevFS();
+ await _launchFromDevFS(_package, _mainPath);
Status restartStatus =
logger.startProgress('Waiting for application to start...');
// Wait for the first frame to be rendered.
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 81d5169..8040103 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -362,9 +362,6 @@
@override
bool get supportsHotMode => true;
- @override
- bool get needsDevFS => true;
-
_IOSSimulatorLogReader _logReader;
_IOSSimulatorDevicePortForwarder _portForwarder;
diff --git a/packages/flutter_tools/lib/src/observatory.dart b/packages/flutter_tools/lib/src/observatory.dart
index bcbb52d..0381ef1 100644
--- a/packages/flutter_tools/lib/src/observatory.dart
+++ b/packages/flutter_tools/lib/src/observatory.dart
@@ -128,9 +128,14 @@
}
}
- Future<String> getFirstViewId() async {
+ Future<List<Map<String, String>>> getViewList() async {
Map<String, dynamic> response = await peer.sendRequest('_flutter.listViews');
List<Map<String, String>> views = response['views'];
+ return views;
+ }
+
+ Future<String> getFirstViewId() async {
+ List<Map<String, String>> views = await getViewList();
return views[0]['id'];
}
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 0eb5aea..0aea86f 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -12,6 +12,7 @@
import 'device.dart';
import 'globals.dart';
import 'observatory.dart';
+import 'view.dart';
// Shared code between different resident application runners.
abstract class ResidentRunner {
@@ -28,6 +29,7 @@
final Completer<int> _finished = new Completer<int>();
Observatory serviceProtocol;
+ ViewManager viewManager;
StreamSubscription<String> _loggingSubscription;
/// Start the app and keep the process running during its lifetime.
@@ -96,6 +98,11 @@
serviceProtocol.onIsolateEvent.listen((Event event) {
printTrace(event.toString());
});
+
+ // Setup view manager and refresh the view list.
+ viewManager = new ViewManager(serviceProtocol);
+ await viewManager.refresh();
+
// Listen for service protocol connection to close.
serviceProtocol.done.whenComplete(() {
appFinished();
diff --git a/packages/flutter_tools/lib/src/view.dart b/packages/flutter_tools/lib/src/view.dart
new file mode 100644
index 0000000..fb4085c
--- /dev/null
+++ b/packages/flutter_tools/lib/src/view.dart
@@ -0,0 +1,89 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'globals.dart';
+import 'observatory.dart';
+
+/// Peered to a Android/iOS FlutterView widget on a device.
+class FlutterView {
+ FlutterView(this.viewId, this.viewManager);
+
+ final String viewId;
+ final ViewManager viewManager;
+ String _uiIsolateId;
+ String get uiIsolateId => _uiIsolateId;
+
+ Future<Null> runFromSource(String entryPath,
+ String packagesPath,
+ String assetsDirectoryPath) async {
+ return viewManager._runFromSource(this,
+ entryPath,
+ packagesPath,
+ assetsDirectoryPath);
+ }
+
+ @override
+ String toString() => viewId;
+
+ @override
+ bool operator ==(FlutterView other) {
+ return other.viewId == viewId;
+ }
+
+ @override
+ int get hashCode => viewId.hashCode;
+}
+
+/// Manager of FlutterViews.
+class ViewManager {
+ ViewManager(this.serviceProtocol);
+
+ final Observatory serviceProtocol;
+
+ Future<Null> refresh() async {
+ List<Map<String, String>> viewList = await serviceProtocol.getViewList();
+ for (Map<String, String> viewDescription in viewList) {
+ FlutterView view = new FlutterView(viewDescription['id'], this);
+ if (!views.contains(view)) {
+ // Canonicalize views against the view set.
+ views.add(view);
+ }
+ }
+ }
+
+ // TODO(johnmccutchan): Report errors when running failed.
+ Future<Null> _runFromSource(FlutterView view,
+ String entryPath,
+ String packagesPath,
+ String assetsDirectoryPath) async {
+ final String viewId = await serviceProtocol.getFirstViewId();
+ // When this completer completes the isolate is running.
+ final Completer<Null> completer = new Completer<Null>();
+ final StreamSubscription<Event> subscription =
+ serviceProtocol.onIsolateEvent.listen((Event event) {
+ // TODO(johnmccutchan): Listen to the debug stream and catch initial
+ // launch errors.
+ if (event.kind == 'IsolateRunnable') {
+ printTrace('Isolate is runnable.');
+ completer.complete(null);
+ }
+ });
+ await serviceProtocol.runInView(viewId,
+ entryPath,
+ packagesPath,
+ assetsDirectoryPath);
+ await completer.future;
+ await subscription.cancel();
+ }
+
+ // TODO(johnmccutchan): Remove this accessor and make the runner multi-view
+ // aware.
+ FlutterView get mainView {
+ return views.first;
+ }
+
+ final Set<FlutterView> views = new Set<FlutterView>();
+}