Commit 728e2a56 authored by John McCutchan's avatar John McCutchan Committed by GitHub

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).
parent b761ffaf
......@@ -365,8 +365,12 @@ class DevFS {
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) {
......
......@@ -192,9 +192,6 @@ abstract class Device {
/// 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,
......
......@@ -23,6 +23,7 @@ import 'devfs.dart';
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 @@ class HotRunner extends ResidentRunner {
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,49 +263,44 @@ class HotRunner extends ResidentRunner {
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;
} 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);
}
printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...');
_loaderShowMessage('Launching...');
await _launchFromDevFS(_package, _mainPath);
} else {
if (connectionInfoCompleter != null)
connectionInfoCompleter.complete(new DebugConnectionInfo(result.observatoryPort));
);
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();
printStatus('Application running.');
......@@ -383,9 +375,6 @@ class HotRunner extends ResidentRunner {
}
Future<Null> _evictDirtyAssets() async {
if (_devFS == null) {
return;
}
if (_devFS.dirtyAssetEntries.length == 0) {
return;
}
......@@ -408,26 +397,8 @@ class HotRunner extends ResidentRunner {
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 @@ class HotRunner extends ResidentRunner {
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.
......
......@@ -362,9 +362,6 @@ class IOSSimulator extends Device {
@override
bool get supportsHotMode => true;
@override
bool get needsDevFS => true;
_IOSSimulatorLogReader _logReader;
_IOSSimulatorDevicePortForwarder _portForwarder;
......
......@@ -128,9 +128,14 @@ class Observatory {
}
}
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'];
}
......
......@@ -12,6 +12,7 @@ import 'build_info.dart';
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 @@ abstract class ResidentRunner {
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 @@ abstract class ResidentRunner {
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();
......
// 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>();
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment