Switch many Device methods to be async (#9587)

`adb` can sometimes hang, which will in turn hang the Dart isolate if
we're using `Process.runSync()`. This changes many of the `Device` methods
to return `Future<T>` in order to allow them to use the async process
methods. A future change will add timeouts to the associated calls so
that we can properly alert the user to the hung `adb` process.

This is work towards #7102, #9567
This commit is contained in:
Todd Volkert 2017-04-25 17:23:00 -07:00 committed by GitHub
parent 596eb033c7
commit 60c5ffc1a9
29 changed files with 249 additions and 241 deletions

View file

@ -51,7 +51,7 @@ class AndroidDevice extends Device {
bool _isLocalEmulator;
TargetPlatform _platform;
String _getProperty(String name) {
Future<String> _getProperty(String name) async {
if (_properties == null) {
_properties = <String, String>{};
@ -79,19 +79,19 @@ class AndroidDevice extends Device {
}
@override
bool get isLocalEmulator {
Future<bool> get isLocalEmulator async {
if (_isLocalEmulator == null) {
final String characteristics = _getProperty('ro.build.characteristics');
final String characteristics = await _getProperty('ro.build.characteristics');
_isLocalEmulator = characteristics != null && characteristics.contains('emulator');
}
return _isLocalEmulator;
}
@override
TargetPlatform get targetPlatform {
Future<TargetPlatform> get targetPlatform async {
if (_platform == null) {
// http://developer.android.com/ndk/guides/abis.html (x86, armeabi-v7a, ...)
switch (_getProperty('ro.product.cpu.abi')) {
switch (await _getProperty('ro.product.cpu.abi')) {
case 'x86_64':
_platform = TargetPlatform.android_x64;
break;
@ -108,11 +108,12 @@ class AndroidDevice extends Device {
}
@override
String get sdkNameAndVersion => 'Android $_sdkVersion (API $_apiVersion)';
Future<String> get sdkNameAndVersion async =>
'Android ${await _sdkVersion} (API ${await _apiVersion})';
String get _sdkVersion => _getProperty('ro.build.version.release');
Future<String> get _sdkVersion => _getProperty('ro.build.version.release');
String get _apiVersion => _getProperty('ro.build.version.sdk');
Future<String> get _apiVersion => _getProperty('ro.build.version.sdk');
_AdbLogReader _logReader;
_AndroidDevicePortForwarder _portForwarder;
@ -160,16 +161,16 @@ class AndroidDevice extends Device {
return false;
}
bool _checkForSupportedAndroidVersion() {
Future<bool> _checkForSupportedAndroidVersion() async {
try {
// If the server is automatically restarted, then we get irrelevant
// output lines like this, which we want to ignore:
// adb server is out of date. killing..
// * daemon started successfully *
runCheckedSync(<String>[getAdbPath(androidSdk), 'start-server']);
await runCheckedAsync(<String>[getAdbPath(androidSdk), 'start-server']);
// Sample output: '22'
final String sdkVersion = _getProperty('ro.build.version.sdk');
final String sdkVersion = await _getProperty('ro.build.version.sdk');
final int sdkVersionParsed = int.parse(sdkVersion, onError: (String source) => null);
if (sdkVersionParsed == null) {
@ -195,8 +196,9 @@ class AndroidDevice extends Device {
return '/data/local/tmp/sky.${app.id}.sha1';
}
String _getDeviceApkSha1(ApplicationPackage app) {
return runSync(adbCommandForDevice(<String>['shell', 'cat', _getDeviceSha1Path(app)]));
Future<String> _getDeviceApkSha1(ApplicationPackage app) async {
final RunResult result = await runAsync(adbCommandForDevice(<String>['shell', 'cat', _getDeviceSha1Path(app)]));
return result.stdout;
}
String _getSourceSha1(ApplicationPackage app) {
@ -209,15 +211,15 @@ class AndroidDevice extends Device {
String get name => modelID;
@override
bool isAppInstalled(ApplicationPackage app) {
Future<bool> isAppInstalled(ApplicationPackage app) async {
// This call takes 400ms - 600ms.
final String listOut = runCheckedSync(adbCommandForDevice(<String>['shell', 'pm', 'list', 'packages', app.id]));
return LineSplitter.split(listOut).contains("package:${app.id}");
final RunResult listOut = await runCheckedAsync(adbCommandForDevice(<String>['shell', 'pm', 'list', 'packages', app.id]));
return LineSplitter.split(listOut.stdout).contains("package:${app.id}");
}
@override
bool isLatestBuildInstalled(ApplicationPackage app) {
final String installedSha1 = _getDeviceApkSha1(app);
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async {
final String installedSha1 = await _getDeviceApkSha1(app);
return installedSha1.isNotEmpty && installedSha1 == _getSourceSha1(app);
}
@ -229,7 +231,7 @@ class AndroidDevice extends Device {
return false;
}
if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
if (!_checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion())
return false;
final Status status = logger.startProgress('Installing ${apk.apkPath}...', expectSlowOperation: true);
@ -249,11 +251,11 @@ class AndroidDevice extends Device {
}
@override
bool uninstallApp(ApplicationPackage app) {
if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
Future<bool> uninstallApp(ApplicationPackage app) async {
if (!_checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion())
return false;
final String uninstallOut = runCheckedSync(adbCommandForDevice(<String>['uninstall', app.id]));
final String uninstallOut = (await runCheckedAsync(adbCommandForDevice(<String>['uninstall', app.id]))).stdout;
final RegExp failureExp = new RegExp(r'^Failure.*$', multiLine: true);
final String failure = failureExp.stringMatch(uninstallOut);
if (failure != null) {
@ -265,9 +267,9 @@ class AndroidDevice extends Device {
}
Future<bool> _installLatestApp(ApplicationPackage package) async {
final bool wasInstalled = isAppInstalled(package);
final bool wasInstalled = await isAppInstalled(package);
if (wasInstalled) {
if (isLatestBuildInstalled(package)) {
if (await isLatestBuildInstalled(package)) {
printTrace('Latest build already installed.');
return true;
}
@ -277,7 +279,7 @@ class AndroidDevice extends Device {
printTrace('Warning: Failed to install APK.');
if (wasInstalled) {
printStatus('Uninstalling old version...');
if (!uninstallApp(package)) {
if (!await uninstallApp(package)) {
printError('Error: Uninstalling old version failed.');
return false;
}
@ -304,10 +306,10 @@ class AndroidDevice extends Device {
bool prebuiltApplication: false,
bool applicationNeedsRebuild: false,
}) async {
if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
if (!_checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion())
return new LaunchResult.failed();
if (targetPlatform != TargetPlatform.android_arm && mode != BuildMode.debug) {
if (await targetPlatform != TargetPlatform.android_arm && mode != BuildMode.debug) {
printError('Profile and release builds are only supported on ARM targets.');
return new LaunchResult.failed();
}
@ -369,7 +371,7 @@ class AndroidDevice extends Device {
cmd.addAll(<String>['--ez', 'use-test-fonts', 'true']);
}
cmd.add(apk.launchActivity);
final String result = runCheckedSync(cmd);
final String result = (await runCheckedAsync(cmd)).stdout;
// This invocation returns 0 even when it fails.
if (result.contains('Error: ')) {
printError(result.trim());
@ -455,16 +457,15 @@ class AndroidDevice extends Device {
bool get supportsScreenshot => true;
@override
Future<Null> takeScreenshot(File outputFile) {
Future<Null> takeScreenshot(File outputFile) async {
const String remotePath = '/data/local/tmp/flutter_screenshot.png';
runCheckedSync(adbCommandForDevice(<String>['shell', 'screencap', '-p', remotePath]));
runCheckedSync(adbCommandForDevice(<String>['pull', remotePath, outputFile.path]));
runCheckedSync(adbCommandForDevice(<String>['shell', 'rm', remotePath]));
return new Future<Null>.value();
await runCheckedAsync(adbCommandForDevice(<String>['shell', 'screencap', '-p', remotePath]));
await runCheckedAsync(adbCommandForDevice(<String>['pull', remotePath, outputFile.path]));
await runCheckedAsync(adbCommandForDevice(<String>['shell', 'rm', remotePath]));
}
@override
Future<List<DiscoveredApp>> discoverApps() {
Future<List<DiscoveredApp>> discoverApps() async {
final RegExp discoverExp = new RegExp(r'DISCOVER: (.*)');
final List<DiscoveredApp> result = <DiscoveredApp>[];
final StreamSubscription<String> logs = getLogReader().logLines.listen((String line) {
@ -475,14 +476,13 @@ class AndroidDevice extends Device {
}
});
runCheckedSync(adbCommandForDevice(<String>[
await runCheckedAsync(adbCommandForDevice(<String>[
'shell', 'am', 'broadcast', '-a', 'io.flutter.view.DISCOVER'
]));
return new Future<List<DiscoveredApp>>.delayed(const Duration(seconds: 1), () {
logs.cancel();
return result;
});
await new Future<Null>.delayed(const Duration(seconds: 1));
logs.cancel();
return result;
}
}
@ -744,7 +744,7 @@ class _AndroidDevicePortForwarder extends DevicePortForwarder {
hostPort = await portScanner.findAvailablePort();
}
runCheckedSync(device.adbCommandForDevice(
await runCheckedAsync(device.adbCommandForDevice(
<String>['forward', 'tcp:$hostPort', 'tcp:$devicePort']
));
@ -753,7 +753,7 @@ class _AndroidDevicePortForwarder extends DevicePortForwarder {
@override
Future<Null> unforward(ForwardedPort forwardedPort) async {
runCheckedSync(device.adbCommandForDevice(
await runCheckedAsync(device.adbCommandForDevice(
<String>['forward', '--remove', 'tcp:${forwardedPort.hostPort}']
));
}

View file

@ -221,6 +221,15 @@ bool exitsHappy(List<String> cli) {
}
}
Future<bool> exitsHappyAsync(List<String> cli) async {
_traceCommand(cli);
try {
return (await processManager.run(cli)).exitCode == 0;
} catch (error) {
return false;
}
}
/// Run cmd and return stdout.
///
/// Throws an error if cmd exits with a non-zero value.
@ -241,16 +250,6 @@ String runCheckedSync(List<String> cmd, {
);
}
/// Run cmd and return stdout on success.
///
/// Throws the standard error output if cmd exits with a non-zero value.
String runSyncAndThrowStdErrOnError(List<String> cmd) {
return _runWithLoggingSync(cmd,
checked: true,
throwStandardErrorOnError: true,
hideStdout: true);
}
/// Run cmd and return stdout.
String runSync(List<String> cmd, {
String workingDirectory,

View file

@ -273,24 +273,24 @@ Future<String> _buildAotSnapshot(
final List<String> commonBuildOptions = <String>['-arch', 'arm64', '-miphoneos-version-min=8.0'];
if (interpreter) {
runCheckedSync(<String>['mv', vmSnapshotData, fs.path.join(outputDir.path, kVmSnapshotData)]);
runCheckedSync(<String>['mv', isolateSnapshotData, fs.path.join(outputDir.path, kIsolateSnapshotData)]);
await runCheckedAsync(<String>['mv', vmSnapshotData, fs.path.join(outputDir.path, kVmSnapshotData)]);
await runCheckedAsync(<String>['mv', isolateSnapshotData, fs.path.join(outputDir.path, kIsolateSnapshotData)]);
runCheckedSync(<String>[
await runCheckedAsync(<String>[
'xxd', '--include', kVmSnapshotData, fs.path.basename(kVmSnapshotDataC)
], workingDirectory: outputDir.path);
runCheckedSync(<String>[
await runCheckedAsync(<String>[
'xxd', '--include', kIsolateSnapshotData, fs.path.basename(kIsolateSnapshotDataC)
], workingDirectory: outputDir.path);
runCheckedSync(<String>['xcrun', 'cc']
await runCheckedAsync(<String>['xcrun', 'cc']
..addAll(commonBuildOptions)
..addAll(<String>['-c', kVmSnapshotDataC, '-o', kVmSnapshotDataO]));
runCheckedSync(<String>['xcrun', 'cc']
await runCheckedAsync(<String>['xcrun', 'cc']
..addAll(commonBuildOptions)
..addAll(<String>['-c', kIsolateSnapshotDataC, '-o', kIsolateSnapshotDataO]));
} else {
runCheckedSync(<String>['xcrun', 'cc']
await runCheckedAsync(<String>['xcrun', 'cc']
..addAll(commonBuildOptions)
..addAll(<String>['-c', assembly, '-o', assemblyO]));
}
@ -313,7 +313,7 @@ Future<String> _buildAotSnapshot(
} else {
linkCommand.add(assemblyO);
}
runCheckedSync(linkCommand);
await runCheckedAsync(linkCommand);
}
return outputPath;

View file

@ -44,7 +44,7 @@ class ConfigCommand extends FlutterCommand {
/// Return `null` to disable tracking of the `config` command.
@override
String get usagePath => null;
Future<String> get usagePath => null;
@override
Future<Null> runCommand() async {

View file

@ -346,7 +346,7 @@ class AppDomain extends Domain {
String packagesFilePath,
String projectAssets,
}) async {
if (device.isLocalEmulator && !isEmulatorBuildMode(options.buildMode))
if (await device.isLocalEmulator && !isEmulatorBuildMode(options.buildMode))
throw '${toTitleCase(getModeName(options.buildMode))} mode is not supported for emulators.';
// We change the current working directory for the duration of the `start` command.
@ -381,7 +381,7 @@ class AppDomain extends Domain {
_sendAppEvent(app, 'start', <String, dynamic>{
'deviceId': device.id,
'directory': projectDirectory,
'supportsRestart': isRestartSupported(enableHotReload, device)
'supportsRestart': isRestartSupported(enableHotReload, device),
});
Completer<DebugConnectionInfo> connectionInfoCompleter;
@ -505,6 +505,8 @@ class AppDomain extends Domain {
}
}
typedef void _DeviceEventHandler(Device device);
/// This domain lets callers list and monitor connected devices.
///
/// It exports a `getDevices()` call, as well as firing `device.added` and
@ -530,15 +532,20 @@ class DeviceDomain extends Domain {
_discoverers.add(deviceDiscovery);
for (PollingDeviceDiscovery discoverer in _discoverers) {
discoverer.onAdded.listen((Device device) {
sendEvent('device.added', _deviceToMap(device));
});
discoverer.onRemoved.listen((Device device) {
sendEvent('device.removed', _deviceToMap(device));
});
discoverer.onAdded.listen(_onDeviceEvent('device.added'));
discoverer.onRemoved.listen(_onDeviceEvent('device.removed'));
}
}
Future<Null> _deviceEvents = new Future<Null>.value();
_DeviceEventHandler _onDeviceEvent(String eventName) {
return (Device device) {
_deviceEvents = _deviceEvents.then((_) async {
sendEvent(eventName, await _deviceToMap(device));
});
};
}
final List<PollingDeviceDiscovery> _discoverers = <PollingDeviceDiscovery>[];
Future<List<Device>> getDevices([Map<String, dynamic> args]) {
@ -638,18 +645,16 @@ void stdoutCommandResponse(Map<String, dynamic> command) {
}
dynamic _jsonEncodeObject(dynamic object) {
if (object is Device)
return _deviceToMap(object);
if (object is OperationResult)
return _operationResultToMap(object);
return object;
}
Map<String, dynamic> _deviceToMap(Device device) {
Future<Map<String, dynamic>> _deviceToMap(Device device) async {
return <String, dynamic>{
'id': device.id,
'name': device.name,
'platform': getNameForTargetPlatform(device.targetPlatform),
'platform': getNameForTargetPlatform(await device.targetPlatform),
'emulator': device.isLocalEmulator
};
}
@ -664,8 +669,6 @@ Map<String, dynamic> _operationResultToMap(OperationResult result) {
dynamic _toJsonable(dynamic obj) {
if (obj is String || obj is int || obj is bool || obj is Map<dynamic, dynamic> || obj is List<dynamic> || obj == null)
return obj;
if (obj is Device)
return obj;
if (obj is OperationResult)
return obj;
return '$obj';

View file

@ -27,7 +27,7 @@ class DevicesCommand extends FlutterCommand {
exitCode: 1);
}
final List<Device> devices = await deviceManager.getAllConnectedDevices();
final List<Device> devices = await deviceManager.getAllConnectedDevices().toList();
if (devices.isEmpty) {
printStatus(
@ -36,7 +36,7 @@ class DevicesCommand extends FlutterCommand {
'potential issues, or visit https://flutter.io/setup/ for troubleshooting tips.');
} else {
printStatus('${devices.length} connected ${pluralize('device', devices.length)}:\n');
Device.printDevices(devices);
await Device.printDevices(devices);
}
}
}

View file

@ -183,7 +183,7 @@ void restoreTargetDeviceFinder() {
}
Future<Device> findTargetDevice() async {
final List<Device> devices = await deviceManager.getDevices();
final List<Device> devices = await deviceManager.getDevices().toList();
if (deviceManager.hasSpecifiedDeviceId) {
if (devices.isEmpty) {
@ -192,7 +192,7 @@ Future<Device> findTargetDevice() async {
}
if (devices.length > 1) {
printStatus("Found ${devices.length} devices with name or id matching '${deviceManager.specifiedDeviceId}':");
Device.printDevices(devices);
await Device.printDevices(devices);
return null;
}
return devices.first;
@ -203,16 +203,24 @@ Future<Device> findTargetDevice() async {
// On Mac we look for the iOS Simulator. If available, we use that. Then
// we look for an Android device. If there's one, we use that. Otherwise,
// we launch a new iOS Simulator.
final Device reusableDevice = devices.firstWhere(
(Device d) => d.isLocalEmulator,
orElse: () {
return devices.firstWhere((Device d) => d is AndroidDevice,
orElse: () => null);
Device reusableDevice;
for (Device device in devices) {
if (await device.isLocalEmulator) {
reusableDevice = device;
break;
}
);
}
if (reusableDevice == null) {
for (Device device in devices) {
if (device is AndroidDevice) {
reusableDevice = device;
break;
}
}
}
if (reusableDevice != null) {
printStatus('Found connected ${reusableDevice.isLocalEmulator ? "emulator" : "device"} "${reusableDevice.name}"; will reuse it.');
printStatus('Found connected ${await reusableDevice.isLocalEmulator ? "emulator" : "device"} "${reusableDevice.name}"; will reuse it.');
return reusableDevice;
}
@ -262,8 +270,8 @@ Future<LaunchResult> _startApp(DriveCommand command) async {
printTrace('Installing application package.');
final ApplicationPackage package = command.applicationPackages
.getPackageForPlatform(command.device.targetPlatform);
if (command.device.isAppInstalled(package))
.getPackageForPlatform(await command.device.targetPlatform);
if (await command.device.isAppInstalled(package))
command.device.uninstallApp(package);
command.device.installApp(package);
@ -335,7 +343,7 @@ void restoreAppStopper() {
Future<bool> _stopApp(DriveCommand command) async {
printTrace('Stopping application.');
final ApplicationPackage package = command.applicationPackages.getPackageForPlatform(command.device.targetPlatform);
final ApplicationPackage package = command.applicationPackages.getPackageForPlatform(await command.device.targetPlatform);
final bool stopped = await command.device.stopApp(package);
await command._deviceLogSubscription?.cancel();
return stopped;

View file

@ -31,7 +31,7 @@ class InstallCommand extends FlutterCommand {
@override
Future<Null> runCommand() async {
final ApplicationPackage package = applicationPackages.getPackageForPlatform(device.targetPlatform);
final ApplicationPackage package = applicationPackages.getPackageForPlatform(await device.targetPlatform);
Cache.releaseLockEarly();
@ -46,9 +46,9 @@ Future<bool> installApp(Device device, ApplicationPackage package, { bool uninst
if (package == null)
return false;
if (uninstall && device.isAppInstalled(package)) {
if (uninstall && await device.isAppInstalled(package)) {
printStatus('Uninstalling old version...');
if (!device.uninstallApp(package))
if (!await device.uninstallApp(package))
printError('Warning: uninstalling old version failed');
}

View file

@ -153,14 +153,14 @@ class RunCommand extends RunCommandBase {
Device device;
@override
String get usagePath {
Future<String> get usagePath async {
final String command = shouldUseHotMode() ? 'hotrun' : name;
if (device == null)
return command;
// Return 'run/ios'.
return '$command/${getNameForTargetPlatform(device.targetPlatform)}';
return '$command/${getNameForTargetPlatform(await device.targetPlatform)}';
}
@override
@ -249,7 +249,7 @@ class RunCommand extends RunCommandBase {
return null;
}
if (device.isLocalEmulator && !isEmulatorBuildMode(getBuildMode()))
if (await device.isLocalEmulator && !isEmulatorBuildMode(getBuildMode()))
throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.');
if (hotMode) {

View file

@ -31,9 +31,10 @@ class StopCommand extends FlutterCommand {
@override
Future<Null> runCommand() async {
final ApplicationPackage app = applicationPackages.getPackageForPlatform(device.targetPlatform);
final TargetPlatform targetPlatform = await device.targetPlatform;
final ApplicationPackage app = applicationPackages.getPackageForPlatform(targetPlatform);
if (app == null) {
final String platformName = getNameForTargetPlatform(device.targetPlatform);
final String platformName = getNameForTargetPlatform(targetPlatform);
throwToolExit('No Flutter application for $platformName found in the current directory.');
}
printStatus('Stopping apps on ${device.name}.');

View file

@ -28,7 +28,7 @@ class UpgradeCommand extends FlutterCommand {
@override
Future<Null> runCommand() async {
try {
runCheckedSync(<String>[
await runCheckedAsync(<String>[
'git', 'rev-parse', '@{u}'
], workingDirectory: Cache.flutterRoot);
} catch (e) {

View file

@ -37,42 +37,40 @@ class DeviceManager {
bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
/// Return the devices with a name or id matching [deviceId].
/// This does a case insentitive compare with [deviceId].
Future<List<Device>> getDevicesById(String deviceId) async {
Stream<Device> getDevicesById(String deviceId) async* {
final Stream<Device> devices = getAllConnectedDevices();
deviceId = deviceId.toLowerCase();
final List<Device> devices = await getAllConnectedDevices();
final Device device = devices.firstWhere(
(Device device) =>
device.id.toLowerCase() == deviceId ||
device.name.toLowerCase() == deviceId,
orElse: () => null);
bool exactlyMatchesDeviceId(Device device) =>
device.id.toLowerCase() == deviceId ||
device.name.toLowerCase() == deviceId;
bool startsWithDeviceId(Device device) =>
device.id.toLowerCase().startsWith(deviceId) ||
device.name.toLowerCase().startsWith(deviceId);
if (device != null)
return <Device>[device];
final Device exactMatch = await devices.firstWhere(
exactlyMatchesDeviceId, defaultValue: () => null);
if (exactMatch != null) {
yield exactMatch;
return;
}
// Match on a id or name starting with [deviceId].
return devices.where((Device device) {
return (device.id.toLowerCase().startsWith(deviceId) ||
device.name.toLowerCase().startsWith(deviceId));
}).toList();
await for (Device device in devices.where(startsWithDeviceId))
yield device;
}
/// Return the list of connected devices, filtered by any user-specified device id.
Future<List<Device>> getDevices() async {
if (specifiedDeviceId == null) {
return getAllConnectedDevices();
} else {
return getDevicesById(specifiedDeviceId);
}
Stream<Device> getDevices() {
return hasSpecifiedDeviceId
? getDevicesById(specifiedDeviceId)
: getAllConnectedDevices();
}
/// Return the list of all connected devices.
Future<List<Device>> getAllConnectedDevices() async {
return _deviceDiscoverers
Stream<Device> getAllConnectedDevices() {
return new Stream<Device>.fromIterable(_deviceDiscoverers
.where((DeviceDiscovery discoverer) => discoverer.supportsPlatform)
.expand((DeviceDiscovery discoverer) => discoverer.devices)
.toList();
.expand((DeviceDiscovery discoverer) => discoverer.devices));
}
}
@ -141,19 +139,19 @@ abstract class Device {
bool get supportsStartPaused => true;
/// Whether it is an emulated device running on localhost.
bool get isLocalEmulator;
Future<bool> get isLocalEmulator;
/// Check if a version of the given app is already installed
bool isAppInstalled(ApplicationPackage app);
Future<bool> isAppInstalled(ApplicationPackage app);
/// Check if the latest build of the [app] is already installed.
bool isLatestBuildInstalled(ApplicationPackage app);
Future<bool> isLatestBuildInstalled(ApplicationPackage app);
/// Install an app package on the current device
Future<bool> installApp(ApplicationPackage app);
/// Uninstall an app package from the current device
bool uninstallApp(ApplicationPackage app);
Future<bool> uninstallApp(ApplicationPackage app);
/// Check if the device is supported by Flutter
bool isSupported();
@ -162,9 +160,10 @@ abstract class Device {
// supported by Flutter, and, if not, why.
String supportMessage() => isSupported() ? "Supported" : "Unsupported";
TargetPlatform get targetPlatform;
/// The device's platform.
Future<TargetPlatform> get targetPlatform;
String get sdkNameAndVersion;
Future<String> get sdkNameAndVersion;
/// Get a log reader for this device.
/// If [app] is specified, this will return a log reader specific to that
@ -238,23 +237,24 @@ abstract class Device {
@override
String toString() => name;
static Iterable<String> descriptions(List<Device> devices) {
static Stream<String> descriptions(List<Device> devices) async* {
if (devices.isEmpty)
return <String>[];
return;
// Extract device information
final List<List<String>> table = <List<String>>[];
for (Device device in devices) {
String supportIndicator = device.isSupported() ? '' : ' (unsupported)';
if (device.isLocalEmulator) {
final String type = device.targetPlatform == TargetPlatform.ios ? 'simulator' : 'emulator';
final TargetPlatform targetPlatform = await device.targetPlatform;
if (await device.isLocalEmulator) {
final String type = targetPlatform == TargetPlatform.ios ? 'simulator' : 'emulator';
supportIndicator += ' ($type)';
}
table.add(<String>[
device.name,
device.id,
'${getNameForTargetPlatform(device.targetPlatform)}',
'${device.sdkNameAndVersion}$supportIndicator',
'${getNameForTargetPlatform(targetPlatform)}',
'${await device.sdkNameAndVersion}$supportIndicator',
]);
}
@ -266,13 +266,13 @@ abstract class Device {
}
// Join columns into lines of text
return table.map((List<String> row) =>
indices.map((int i) => row[i].padRight(widths[i])).join('') +
'${row.last}');
for (List<String> row in table) {
yield indices.map((int i) => row[i].padRight(widths[i])).join('') + '${row.last}';
}
}
static void printDevices(List<Device> devices) {
descriptions(devices).forEach(printStatus);
static Future<Null> printDevices(List<Device> devices) async {
await descriptions(devices).forEach(printStatus);
}
}

View file

@ -486,12 +486,12 @@ class DeviceValidator extends DoctorValidator {
@override
Future<ValidationResult> validate() async {
final List<Device> devices = await deviceManager.getAllConnectedDevices();
final List<Device> devices = await deviceManager.getAllConnectedDevices().toList();
List<ValidationMessage> messages;
if (devices.isEmpty) {
messages = <ValidationMessage>[new ValidationMessage('None')];
} else {
messages = Device.descriptions(devices)
messages = await Device.descriptions(devices)
.map((String msg) => new ValidationMessage(msg)).toList();
}
return new ValidationResult(ValidationType.installed, messages);

View file

@ -37,22 +37,22 @@ class FuchsiaDevice extends Device {
final String name;
@override
bool get isLocalEmulator => false;
Future<bool> get isLocalEmulator async => false;
@override
bool get supportsStartPaused => false;
@override
bool isAppInstalled(ApplicationPackage app) => false;
Future<bool> isAppInstalled(ApplicationPackage app) async => false;
@override
bool isLatestBuildInstalled(ApplicationPackage app) => false;
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
@override
Future<bool> installApp(ApplicationPackage app) => new Future<bool>.value(false);
@override
bool uninstallApp(ApplicationPackage app) => false;
Future<bool> uninstallApp(ApplicationPackage app) async => false;
@override
bool isSupported() => true;
@ -77,10 +77,10 @@ class FuchsiaDevice extends Device {
}
@override
TargetPlatform get targetPlatform => TargetPlatform.fuchsia;
Future<TargetPlatform> get targetPlatform async => TargetPlatform.fuchsia;
@override
String get sdkNameAndVersion => 'Fuchsia';
Future<String> get sdkNameAndVersion async => 'Fuchsia';
_FuchsiaLogReader _logReader;
@override

View file

@ -87,7 +87,7 @@ class IOSDevice extends Device {
_IOSDevicePortForwarder _portForwarder;
@override
bool get isLocalEmulator => false;
Future<bool> get isLocalEmulator async => false;
@override
bool get supportsStartPaused => false;
@ -139,10 +139,10 @@ class IOSDevice extends Device {
}
@override
bool isAppInstalled(ApplicationPackage app) {
Future<bool> isAppInstalled(ApplicationPackage app) async {
try {
final String apps = runCheckedSync(<String>[installerPath, '--list-apps']);
if (new RegExp(app.id, multiLine: true).hasMatch(apps)) {
final RunResult apps = await runCheckedAsync(<String>[installerPath, '--list-apps']);
if (new RegExp(app.id, multiLine: true).hasMatch(apps.stdout)) {
return true;
}
} catch (e) {
@ -152,7 +152,7 @@ class IOSDevice extends Device {
}
@override
bool isLatestBuildInstalled(ApplicationPackage app) => false;
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
@override
Future<bool> installApp(ApplicationPackage app) async {
@ -164,7 +164,7 @@ class IOSDevice extends Device {
}
try {
runCheckedSync(<String>[installerPath, '-i', iosApp.deviceBundlePath]);
await runCheckedAsync(<String>[installerPath, '-i', iosApp.deviceBundlePath]);
return true;
} catch (e) {
return false;
@ -172,9 +172,9 @@ class IOSDevice extends Device {
}
@override
bool uninstallApp(ApplicationPackage app) {
Future<bool> uninstallApp(ApplicationPackage app) async {
try {
runCheckedSync(<String>[installerPath, '-U', app.id]);
await runCheckedAsync(<String>[installerPath, '-U', app.id]);
return true;
} catch (e) {
return false;
@ -343,10 +343,10 @@ class IOSDevice extends Device {
}
@override
TargetPlatform get targetPlatform => TargetPlatform.ios;
Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
@override
String get sdkNameAndVersion => 'iOS $_sdkVersion ($_buildVersion)';
Future<String> get sdkNameAndVersion async => 'iOS $_sdkVersion ($_buildVersion)';
String get _sdkVersion => _getDeviceInfo(id, 'ProductVersion');
@ -370,8 +370,7 @@ class IOSDevice extends Device {
@override
Future<Null> takeScreenshot(File outputFile) {
runCheckedSync(<String>[screenshotPath, outputFile.path]);
return new Future<Null>.value();
return runCheckedAsync(<String>[screenshotPath, outputFile.path]);
}
}

View file

@ -165,7 +165,7 @@ class IOSWorkflow extends DoctorValidator implements Workflow {
// Check for compatibility between libimobiledevice and Xcode.
// TODO(cbracken) remove this check once libimobiledevice > 1.2.0 is released.
final ProcessResult result = (await runAsync(<String>['idevice_id', '-l'])).processResult;
if (result.exitCode == 0 && result.stdout.isNotEmpty && !exitsHappy(<String>['ideviceName'])) {
if (result.exitCode == 0 && result.stdout.isNotEmpty && !await exitsHappyAsync(<String>['ideviceName'])) {
brewStatus = ValidationType.partial;
messages.add(new ValidationMessage.error(
'libimobiledevice is incompatible with the installed Xcode version. To update, run:\n'

View file

@ -399,7 +399,7 @@ Future<Null> _copyServiceFrameworks(List<Map<String, String>> services, Director
continue;
}
// Shell out so permissions on the dylib are preserved.
runCheckedSync(<String>['/bin/cp', dylib.path, frameworksDirectory.path]);
await runCheckedAsync(<String>['/bin/cp', dylib.path, frameworksDirectory.path]);
}
}

View file

@ -224,8 +224,8 @@ class SimControl {
bool _isAnyConnected() => getConnectedDevices().isNotEmpty;
bool isInstalled(String appId) {
return exitsHappy(<String>[
Future<bool> isInstalled(String appId) {
return exitsHappyAsync(<String>[
_xcrunPath,
'simctl',
'get_app_container',
@ -234,23 +234,23 @@ class SimControl {
]);
}
void install(String deviceId, String appPath) {
runCheckedSync(<String>[_xcrunPath, 'simctl', 'install', deviceId, appPath]);
Future<Null> install(String deviceId, String appPath) {
return runCheckedAsync(<String>[_xcrunPath, 'simctl', 'install', deviceId, appPath]);
}
void uninstall(String deviceId, String appId) {
runCheckedSync(<String>[_xcrunPath, 'simctl', 'uninstall', deviceId, appId]);
Future<Null> uninstall(String deviceId, String appId) {
return runCheckedAsync(<String>[_xcrunPath, 'simctl', 'uninstall', deviceId, appId]);
}
void launch(String deviceId, String appIdentifier, [List<String> launchArgs]) {
Future<Null> launch(String deviceId, String appIdentifier, [List<String> launchArgs]) {
final List<String> args = <String>[_xcrunPath, 'simctl', 'launch', deviceId, appIdentifier];
if (launchArgs != null)
args.addAll(launchArgs);
runCheckedSync(args);
return runCheckedAsync(args);
}
void takeScreenshot(String outputPath) {
runCheckedSync(<String>[_xcrunPath, 'simctl', 'io', 'booted', 'screenshot', outputPath]);
Future<Null> takeScreenshot(String outputPath) {
return runCheckedAsync(<String>[_xcrunPath, 'simctl', 'io', 'booted', 'screenshot', outputPath]);
}
}
@ -313,7 +313,7 @@ class IOSSimulator extends Device {
final String category;
@override
bool get isLocalEmulator => true;
Future<bool> get isLocalEmulator async => true;
@override
bool get supportsHotMode => true;
@ -335,12 +335,12 @@ class IOSSimulator extends Device {
}
@override
bool isAppInstalled(ApplicationPackage app) {
Future<bool> isAppInstalled(ApplicationPackage app) {
return SimControl.instance.isInstalled(app.id);
}
@override
bool isLatestBuildInstalled(ApplicationPackage app) => false;
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
@override
Future<bool> installApp(ApplicationPackage app) async {
@ -354,9 +354,9 @@ class IOSSimulator extends Device {
}
@override
bool uninstallApp(ApplicationPackage app) {
Future<bool> uninstallApp(ApplicationPackage app) async {
try {
SimControl.instance.uninstall(id, app.id);
await SimControl.instance.uninstall(id, app.id);
return true;
} catch (e) {
return false;
@ -495,21 +495,18 @@ class IOSSimulator extends Device {
}
}
bool _applicationIsInstalledAndRunning(ApplicationPackage app) {
final bool isInstalled = isAppInstalled(app);
final bool isRunning = exitsHappy(<String>[
'/usr/bin/killall',
'Runner',
Future<bool> _applicationIsInstalledAndRunning(ApplicationPackage app) async {
final List<bool> criteria = await Future.wait(<Future<bool>>[
isAppInstalled(app),
exitsHappyAsync(<String>['/usr/bin/killall', 'Runner']),
]);
return isInstalled && isRunning;
return criteria.reduce((bool a, bool b) => a && b);
}
Future<Null> _setupUpdatedApplicationBundle(ApplicationPackage app) async {
await _sideloadUpdatedAssetsForInstalledApplicationBundle(app);
if (!_applicationIsInstalledAndRunning(app))
if (!await _applicationIsInstalledAndRunning(app))
return _buildAndInstallApplicationBundle(app);
}
@ -544,7 +541,7 @@ class IOSSimulator extends Device {
ApplicationPackage app, String localFile, String targetFile) async {
if (platform.isMacOS) {
final String simulatorHomeDirectory = _getSimulatorAppHomeDirectory(app);
runCheckedSync(<String>['cp', localFile, fs.path.join(simulatorHomeDirectory, targetFile)]);
await runCheckedAsync(<String>['cp', localFile, fs.path.join(simulatorHomeDirectory, targetFile)]);
return true;
}
return false;
@ -555,10 +552,10 @@ class IOSSimulator extends Device {
}
@override
TargetPlatform get targetPlatform => TargetPlatform.ios;
Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
@override
String get sdkNameAndVersion => category;
Future<String> get sdkNameAndVersion async => category;
@override
DeviceLogReader getLogReader({ApplicationPackage app}) {
@ -595,8 +592,7 @@ class IOSSimulator extends Device {
@override
Future<Null> takeScreenshot(File outputFile) {
SimControl.instance.takeScreenshot(outputFile.path);
return new Future<Null>.value();
return SimControl.instance.takeScreenshot(outputFile.path);
}
}

View file

@ -61,11 +61,12 @@ class ColdRunner extends ResidentRunner {
printStatus('Launching ${getDisplayPath(mainPath)} on ${device.name} in $modeName mode...');
}
package = getApplicationPackageForPlatform(device.targetPlatform, applicationBinary: applicationBinary);
final TargetPlatform targetPlatform = await device.targetPlatform;
package = getApplicationPackageForPlatform(targetPlatform, applicationBinary: applicationBinary);
if (package == null) {
String message = 'No application found for ${device.targetPlatform}.';
final String hint = getMissingPackageHintForPlatform(device.targetPlatform);
String message = 'No application found for $targetPlatform.';
final String hint = getMissingPackageHintForPlatform(targetPlatform);
if (hint != null)
message += '\n$hint';
printError(message);

View file

@ -177,11 +177,12 @@ class HotRunner extends ResidentRunner {
final String modeName = getModeName(debuggingOptions.buildMode);
printStatus('Launching ${getDisplayPath(mainPath)} on ${device.name} in $modeName mode...');
package = getApplicationPackageForPlatform(device.targetPlatform, applicationBinary: applicationBinary);
final TargetPlatform targetPlatform = await device.targetPlatform;
package = getApplicationPackageForPlatform(targetPlatform, applicationBinary: applicationBinary);
if (package == null) {
String message = 'No application found for ${device.targetPlatform}.';
final String hint = getMissingPackageHintForPlatform(device.targetPlatform);
String message = 'No application found for $targetPlatform.';
final String hint = getMissingPackageHintForPlatform(targetPlatform);
if (hint != null)
message += '\n$hint';
printError(message);

View file

@ -102,7 +102,7 @@ abstract class FlutterCommand extends Command<Null> {
/// The path to send to Google Analytics. Return `null` here to disable
/// tracking of the command.
String get usagePath => name;
Future<String> get usagePath async => name;
/// Runs this command.
///
@ -144,9 +144,9 @@ abstract class FlutterCommand extends Command<Null> {
setupApplicationPackages();
final String commandPath = usagePath;
final String commandPath = await usagePath;
if (commandPath != null)
flutterUsage.sendCommand(usagePath);
flutterUsage.sendCommand(commandPath);
await runCommand();
}
@ -158,14 +158,14 @@ abstract class FlutterCommand extends Command<Null> {
/// devices and criteria entered by the user on the command line.
/// If a device cannot be found that meets specified criteria,
/// then print an error message and return `null`.
Future<Device> findTargetDevice({bool androidOnly: false}) async {
Future<Device> findTargetDevice() async {
if (!doctor.canLaunchAnything) {
printError("Unable to locate a development device; please run 'flutter doctor' "
"for information about installing additional components.");
return null;
}
List<Device> devices = await deviceManager.getDevices();
List<Device> devices = await deviceManager.getDevices().toList();
if (devices.isEmpty && deviceManager.hasSpecifiedDeviceId) {
printStatus("No devices found with name or id "
@ -178,9 +178,6 @@ abstract class FlutterCommand extends Command<Null> {
devices = devices.where((Device device) => device.isSupported()).toList();
if (androidOnly)
devices = devices.where((Device device) => device.targetPlatform == TargetPlatform.android_arm).toList();
if (devices.isEmpty) {
printStatus('No supported devices connected.');
return null;
@ -191,10 +188,10 @@ abstract class FlutterCommand extends Command<Null> {
} else {
printStatus("More than one device connected; please specify a device with "
"the '-d <deviceId>' flag.");
devices = await deviceManager.getAllConnectedDevices();
devices = await deviceManager.getAllConnectedDevices().toList();
}
printStatus('');
Device.printDevices(devices);
await Device.printDevices(devices);
return null;
}
return devices.single;

View file

@ -4,6 +4,7 @@
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:usage/usage_io.dart';
import 'base/context.dart';
@ -96,7 +97,8 @@ class Usage {
_analytics.sendException('${exception.runtimeType}\n${sanitizeStacktrace(trace)}');
}
/// Fires whenever analytics data is sent over the network; public for testing.
/// Fires whenever analytics data is sent over the network.
@visibleForTesting
Stream<Map<String, dynamic>> get onSend => _analytics.onSend;
/// Returns when the last analytics event has been sent, or after a fixed

View file

@ -86,7 +86,7 @@ class _ZipToolBuilder extends ZipBuilder {
final Iterable<String> compressedNames = _getCompressedNames();
if (compressedNames.isNotEmpty) {
runCheckedSync(
await runCheckedAsync(
<String>['zip', '-q', outFile.absolute.path]..addAll(compressedNames),
workingDirectory: zipBuildDir.path
);
@ -94,7 +94,7 @@ class _ZipToolBuilder extends ZipBuilder {
final Iterable<String> storedNames = _getStoredNames();
if (storedNames.isNotEmpty) {
runCheckedSync(
await runCheckedAsync(
<String>['zip', '-q', '-0', outFile.absolute.path]..addAll(storedNames),
workingDirectory: zipBuildDir.path
);

View file

@ -56,7 +56,7 @@ void main() {
Usage: () => new Usage(),
});
// Ensure we con't send for the 'flutter config' command.
// Ensure we don't send for the 'flutter config' command.
testUsingContext('config doesn\'t send', () async {
int count = 0;
flutterUsage.onSend.listen((Map<String, dynamic> data) => count++);

View file

@ -14,7 +14,7 @@ void main() {
testUsingContext('getDevices', () async {
// Test that DeviceManager.getDevices() doesn't throw.
final DeviceManager deviceManager = new DeviceManager();
final List<Device> devices = await deviceManager.getDevices();
final List<Device> devices = await deviceManager.getDevices().toList();
expect(devices, isList);
});
@ -26,7 +26,7 @@ void main() {
final DeviceManager deviceManager = new TestDeviceManager(devices);
Future<Null> expectDevice(String id, List<Device> expected) async {
expect(await deviceManager.getDevicesById(id), expected);
expect(await deviceManager.getDevicesById(id).toList(), expected);
}
expectDevice('01abfc49119c410e', <Device>[device2]);
expectDevice('Nexus 5X', <Device>[device2]);
@ -44,8 +44,8 @@ class TestDeviceManager extends DeviceManager {
TestDeviceManager(this.allDevices);
@override
Future<List<Device>> getAllConnectedDevices() async {
return allDevices;
Stream<Device> getAllConnectedDevices() {
return new Stream<Device>.fromIterable(allDevices);
}
}

View file

@ -134,20 +134,19 @@ class MockDeviceManager implements DeviceManager {
bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
@override
Future<List<Device>> getAllConnectedDevices() => new Future<List<Device>>.value(devices);
Stream<Device> getAllConnectedDevices() => new Stream<Device>.fromIterable(devices);
@override
Future<List<Device>> getDevicesById(String deviceId) async {
return devices.where((Device device) => device.id == deviceId).toList();
Stream<Device> getDevicesById(String deviceId) {
return new Stream<Device>.fromIterable(
devices.where((Device device) => device.id == deviceId));
}
@override
Future<List<Device>> getDevices() async {
if (specifiedDeviceId == null) {
return getAllConnectedDevices();
} else {
return getDevicesById(specifiedDeviceId);
}
Stream<Device> getDevices() {
return hasSpecifiedDeviceId
? getDevicesById(specifiedDeviceId)
: getAllConnectedDevices();
}
void addDevice(Device device) => devices.add(device);

View file

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io' show ProcessResult;
import 'package:file/file.dart';
@ -38,23 +39,23 @@ void main() {
// Let everything else return exit code 0 so process.dart doesn't crash.
// The matcher order is important.
when(
mockProcessManager.runSync(any, environment: null, workingDirectory: null)
mockProcessManager.run(any, environment: null, workingDirectory: null)
).thenReturn(
new ProcessResult(2, 0, '', null)
new Future<ProcessResult>.value(new ProcessResult(2, 0, '', ''))
);
// Let `which idevicescreenshot` fail with exit code 1.
when(
mockProcessManager.runSync(
<String>['which', 'idevicescreenshot'], environment: null, workingDirectory: null)
).thenReturn(
new ProcessResult(1, 1, '', null)
new ProcessResult(1, 1, '', '')
);
iosDeviceUnderTest = new IOSDevice('1234');
iosDeviceUnderTest.takeScreenshot(mockOutputFile);
await iosDeviceUnderTest.takeScreenshot(mockOutputFile);
verify(mockProcessManager.runSync(
<String>['which', 'idevicescreenshot'], environment: null, workingDirectory: null));
verifyNever(mockProcessManager.runSync(
verifyNever(mockProcessManager.run(
<String>['idevicescreenshot', fs.path.join('some', 'test', 'path', 'image.png')],
environment: null,
workingDirectory: null
@ -74,23 +75,23 @@ void main() {
// Let everything else return exit code 0.
// The matcher order is important.
when(
mockProcessManager.runSync(any, environment: null, workingDirectory: null)
mockProcessManager.run(any, environment: null, workingDirectory: null)
).thenReturn(
new ProcessResult(4, 0, '', null)
new Future<ProcessResult>.value(new ProcessResult(4, 0, '', ''))
);
// Let there be idevicescreenshot in the PATH.
when(
mockProcessManager.runSync(
<String>['which', 'idevicescreenshot'], environment: null, workingDirectory: null)
).thenReturn(
new ProcessResult(3, 0, fs.path.join('some', 'path', 'to', 'iscreenshot'), null)
new ProcessResult(3, 0, fs.path.join('some', 'path', 'to', 'iscreenshot'), '')
);
iosDeviceUnderTest = new IOSDevice('1234');
iosDeviceUnderTest.takeScreenshot(mockOutputFile);
await iosDeviceUnderTest.takeScreenshot(mockOutputFile);
verify(mockProcessManager.runSync(
<String>['which', 'idevicescreenshot'], environment: null, workingDirectory: null));
verify(mockProcessManager.runSync(
verify(mockProcessManager.run(
<String>[
fs.path.join('some', 'path', 'to', 'iscreenshot'),
fs.path.join('some', 'test', 'path', 'image.png')

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io' show ProcessResult;
import 'package:file/file.dart';
@ -128,9 +129,9 @@ void main() {
mockProcessManager = new MockProcessManager();
// Let everything else return exit code 0 so process.dart doesn't crash.
when(
mockProcessManager.runSync(any, environment: null, workingDirectory: null)
mockProcessManager.run(any, environment: null, workingDirectory: null)
).thenReturn(
new ProcessResult(2, 0, '', null)
new Future<ProcessResult>.value(new ProcessResult(2, 0, '', ''))
);
// Doesn't matter what the device is.
deviceUnderTest = new IOSSimulator('x', name: 'iPhone SE');
@ -148,14 +149,14 @@ void main() {
testUsingContext(
'Xcode 8.2+ supports screenshots',
() {
() async {
when(mockXcode.xcodeMajorVersion).thenReturn(8);
when(mockXcode.xcodeMinorVersion).thenReturn(2);
expect(deviceUnderTest.supportsScreenshot, true);
final MockFile mockFile = new MockFile();
when(mockFile.path).thenReturn(fs.path.join('some', 'path', 'to', 'screenshot.png'));
deviceUnderTest.takeScreenshot(mockFile);
verify(mockProcessManager.runSync(
await deviceUnderTest.takeScreenshot(mockFile);
verify(mockProcessManager.run(
<String>[
'/usr/bin/xcrun',
'simctl',

View file

@ -31,7 +31,7 @@ class MockApplicationPackageStore extends ApplicationPackageStore {
class MockAndroidDevice extends Mock implements AndroidDevice {
@override
TargetPlatform get targetPlatform => TargetPlatform.android_arm;
Future<TargetPlatform> get targetPlatform async => TargetPlatform.android_arm;
@override
bool isSupported() => true;
@ -39,7 +39,7 @@ class MockAndroidDevice extends Mock implements AndroidDevice {
class MockIOSDevice extends Mock implements IOSDevice {
@override
TargetPlatform get targetPlatform => TargetPlatform.ios;
Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
@override
bool isSupported() => true;
@ -47,7 +47,7 @@ class MockIOSDevice extends Mock implements IOSDevice {
class MockIOSSimulator extends Mock implements IOSSimulator {
@override
TargetPlatform get targetPlatform => TargetPlatform.ios;
Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
@override
bool isSupported() => true;