Merge pull request #2695 from abarth/inherit_render_object_widget
Let RenderObjectWidgets use inherited properties
diff --git a/examples/hello_android/README.md b/examples/hello_android/README.md
new file mode 100644
index 0000000..c7d64c4
--- /dev/null
+++ b/examples/hello_android/README.md
@@ -0,0 +1,16 @@
+# Example of building a Flutter app for Android using Gradle
+
+This project demonstrates how to embed Flutter within an Android application
+and build the Android and Flutter components with Gradle.
+
+To build this project:
+
+* Create a `local.properties` file with these entries:
+ * `sdk.dir=[path to the Android SDK]`
+ * `flutter.sdk=[path to the Flutter SDK]`
+ * `flutter.jar=[path to the flutter.jar file in your build of the Flutter engine]`
+
+Then run:
+
+* `gradle wrapper`
+* `./gradlew build`
diff --git a/examples/hello_android/app/build.gradle b/examples/hello_android/app/build.gradle
new file mode 100644
index 0000000..8ece56f
--- /dev/null
+++ b/examples/hello_android/app/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'com.android.application'
+apply plugin: 'flutter'
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion '22.0.1'
+
+ lintOptions {
+ disable 'InvalidPackage'
+ }
+}
+
+flutter {
+ source 'src/flutter'
+}
diff --git a/examples/hello_android/app/src/flutter/lib/main.dart b/examples/hello_android/app/src/flutter/lib/main.dart
new file mode 100644
index 0000000..151b1da
--- /dev/null
+++ b/examples/hello_android/app/src/flutter/lib/main.dart
@@ -0,0 +1,7 @@
+// 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 'package:flutter/widgets.dart';
+
+void main() => runApp(new Center(child: new Text('Hello from Flutter!')));
diff --git a/examples/hello_android/app/src/flutter/pubspec.yaml b/examples/hello_android/app/src/flutter/pubspec.yaml
new file mode 100644
index 0000000..5669909
--- /dev/null
+++ b/examples/hello_android/app/src/flutter/pubspec.yaml
@@ -0,0 +1,4 @@
+name: gradle
+dependencies:
+ flutter:
+ path: ../../../../../packages/flutter
diff --git a/examples/hello_android/app/src/main/AndroidManifest.xml b/examples/hello_android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a272e14
--- /dev/null
+++ b/examples/hello_android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.flutter"
+ android:versionCode="1"
+ android:versionName="1.0.0" >
+
+ <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="21" />
+ <uses-permission android:name="android.permission.INTERNET"/>
+
+ <application android:name="org.domokit.sky.shell.SkyApplication" android:label="@string/app_name" >
+ <activity
+ android:name=".FlutterActivity"
+ android:label="@string/app_name"
+ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
+ android:launchMode="singleTask"
+ android:theme="@android:style/Theme.Black.NoTitleBar"
+ android:windowSoftInputMode="adjustResize">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/examples/hello_android/app/src/main/java/com/example/flutter/FlutterActivity.java b/examples/hello_android/app/src/main/java/com/example/flutter/FlutterActivity.java
new file mode 100644
index 0000000..7323d6f
--- /dev/null
+++ b/examples/hello_android/app/src/main/java/com/example/flutter/FlutterActivity.java
@@ -0,0 +1,39 @@
+// 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.
+
+package com.example.flutter;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import org.chromium.base.PathUtils;
+import org.domokit.sky.shell.SkyApplication;
+import org.domokit.sky.shell.SkyMain;
+import org.domokit.sky.shell.PlatformViewAndroid;
+
+import java.io.File;
+
+public class FlutterActivity extends Activity {
+ private PlatformViewAndroid flutterView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ SkyMain.ensureInitialized(getApplicationContext(), null);
+ setContentView(R.layout.flutter_layout);
+
+ flutterView = (PlatformViewAndroid) findViewById(R.id.flutter_view);
+ File appBundle = new File(PathUtils.getDataDirectory(this), SkyApplication.APP_BUNDLE);
+ flutterView.runFromBundle(appBundle.getPath(), null);
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (flutterView != null) {
+ flutterView.destroy();
+ }
+ super.onDestroy();
+ }
+}
diff --git a/examples/hello_android/app/src/main/res/layout/flutter_layout.xml b/examples/hello_android/app/src/main/res/layout/flutter_layout.xml
new file mode 100644
index 0000000..bc8c11d
--- /dev/null
+++ b/examples/hello_android/app/src/main/res/layout/flutter_layout.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <TextView
+ android:id="@+id/text_view"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/title"
+ />
+ <org.domokit.sky.shell.PlatformViewAndroid
+ android:id="@+id/flutter_view"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ />
+</LinearLayout>
diff --git a/examples/hello_android/app/src/main/res/values/strings.xml b/examples/hello_android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..670cfae
--- /dev/null
+++ b/examples/hello_android/app/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">Flutter App</string>
+ <string name="title">Flutter Application</string>
+</resources>
diff --git a/examples/hello_android/build.gradle b/examples/hello_android/build.gradle
new file mode 100644
index 0000000..ba21767
--- /dev/null
+++ b/examples/hello_android/build.gradle
@@ -0,0 +1,17 @@
+buildscript {
+ repositories {
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.5.0'
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
+
+task wrapper(type: Wrapper) {
+ gradleVersion = '2.8'
+}
diff --git a/examples/hello_android/buildSrc/build.gradle b/examples/hello_android/buildSrc/build.gradle
new file mode 100644
index 0000000..b62a213
--- /dev/null
+++ b/examples/hello_android/buildSrc/build.gradle
@@ -0,0 +1,7 @@
+repositories {
+ jcenter()
+}
+
+dependencies {
+ compile "com.android.tools.build:gradle:1.5.0"
+}
diff --git a/examples/hello_android/buildSrc/src/main/groovy/FlutterPlugin.groovy b/examples/hello_android/buildSrc/src/main/groovy/FlutterPlugin.groovy
new file mode 100644
index 0000000..bdd0c2e
--- /dev/null
+++ b/examples/hello_android/buildSrc/src/main/groovy/FlutterPlugin.groovy
@@ -0,0 +1,107 @@
+// 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.
+
+package org.domokit.sky.gradle
+
+import com.android.builder.model.AndroidProject
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.Plugin
+import org.gradle.api.Task
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+
+class FlutterPlugin implements Plugin<Project> {
+ private File sdkDir
+
+ @Override
+ void apply(Project project) {
+ Properties properties = new Properties()
+ properties.load(project.rootProject.file("local.properties").newDataInputStream())
+
+ String enginePath = properties.getProperty("flutter.jar")
+ if (enginePath == null) {
+ throw new GradleException("flutter.jar must be defined in local.properties")
+ }
+ FileCollection flutterEngine = project.files(enginePath)
+ if (!flutterEngine.singleFile.isFile()) {
+ throw new GradleException("flutter.jar must point to a Flutter engine JAR")
+ }
+
+ String sdkPath = properties.getProperty("flutter.sdk")
+ if (sdkPath == null) {
+ throw new GradleException("flutter.sdk must be defined in local.properties")
+ }
+ sdkDir = project.file(sdkPath)
+ if (!sdkDir.isDirectory()) {
+ throw new GradleException("flutter.sdk must point to the Flutter SDK directory")
+ }
+
+ project.extensions.create("flutter", FlutterExtension)
+ project.dependencies.add("compile", flutterEngine)
+ project.afterEvaluate this.&addFlutterTask
+ }
+
+ private void addFlutterTask(Project project) {
+ if (project.flutter.source == null) {
+ throw new GradleException("Must provide Flutter source directory")
+ }
+
+ FlutterTask flutterTask = project.tasks.create("flutterBuild", FlutterTask) {
+ sdkDir this.sdkDir
+ sourceDir project.file(project.flutter.source)
+ intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter")
+ }
+
+ project.android.applicationVariants.all { variant ->
+ Task copyFlxTask = project.tasks.create(name: "copyFlx${variant.name.capitalize()}", type: Copy) {
+ dependsOn flutterTask
+ dependsOn variant.mergeAssets
+ from flutterTask.flxPath
+ into variant.mergeAssets.outputDir
+ }
+ variant.outputs[0].processResources.dependsOn(copyFlxTask)
+ }
+ }
+}
+
+class FlutterExtension {
+ String source
+}
+
+class FlutterTask extends DefaultTask {
+ File sdkDir
+
+ @InputDirectory
+ File sourceDir
+
+ @OutputDirectory
+ File intermediateDir
+
+ String getFlxPath() {
+ return "${intermediateDir}/app.flx"
+ }
+
+ @TaskAction
+ void build() {
+ if (!sourceDir.isDirectory()) {
+ throw new GradleException("Invalid Flutter source directory: ${sourceDir}")
+ }
+
+ intermediateDir.mkdirs()
+ project.exec {
+ executable "${sdkDir}/bin/flutter"
+ workingDir sourceDir
+ args "build"
+ args "-o", flxPath
+ args "--snapshot", "${intermediateDir}/snapshot_blob.bin"
+ args "--depfile", "${intermediateDir}/snapshot_blob.bin.d"
+ args "--working-dir", "${intermediateDir}/flx"
+ }
+ }
+}
diff --git a/examples/hello_android/buildSrc/src/main/resources/META-INF/gradle-plugins/flutter.properties b/examples/hello_android/buildSrc/src/main/resources/META-INF/gradle-plugins/flutter.properties
new file mode 100644
index 0000000..f8219be
--- /dev/null
+++ b/examples/hello_android/buildSrc/src/main/resources/META-INF/gradle-plugins/flutter.properties
@@ -0,0 +1 @@
+implementation-class=org.domokit.sky.gradle.FlutterPlugin
diff --git a/examples/hello_android/settings.gradle b/examples/hello_android/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/examples/hello_android/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/examples/stocks/test_driver/scroll_perf.dart b/examples/stocks/test_driver/scroll_perf.dart
index 623f7b7..8188a55 100644
--- a/examples/stocks/test_driver/scroll_perf.dart
+++ b/examples/stocks/test_driver/scroll_perf.dart
@@ -3,11 +3,9 @@
// found in the LICENSE file.
import 'package:flutter_driver/driver_extension.dart';
-import 'package:flutter_driver/src/error.dart';
import 'package:stocks/main.dart' as app;
void main() {
- flutterDriverLog.listen(print);
enableFlutterDriverExtension();
app.main();
}
diff --git a/examples/stocks/test_driver/scroll_perf_test.dart b/examples/stocks/test_driver/scroll_perf_test.dart
index 6165417..a6b3a30 100644
--- a/examples/stocks/test_driver/scroll_perf_test.dart
+++ b/examples/stocks/test_driver/scroll_perf_test.dart
@@ -19,22 +19,29 @@
driver.close();
});
- test('tap on the floating action button; verify counter', () async {
- // Find the scrollable stock list
- ObjectRef stockList = await driver.findByValueKey('stock-list');
- expect(stockList, isNotNull);
+ test('measure', () async {
+ Map<String, dynamic> profileJson = await driver.traceAction(() async {
+ // Find the scrollable stock list
+ ObjectRef stockList = await driver.findByValueKey('stock-list');
+ expect(stockList, isNotNull);
- // Scroll down 5 times
- for (int i = 0; i < 5; i++) {
- await driver.scroll(stockList, 0.0, -300.0, new Duration(milliseconds: 300));
- await new Future<Null>.delayed(new Duration(milliseconds: 500));
- }
+ // Scroll down
+ for (int i = 0; i < 5; i++) {
+ await driver.scroll(stockList, 0.0, -300.0, new Duration(milliseconds: 300));
+ await new Future<Null>.delayed(new Duration(milliseconds: 500));
+ }
- // Scroll up 5 times
- for (int i = 0; i < 5; i++) {
- await driver.scroll(stockList, 0.0, 300.0, new Duration(milliseconds: 300));
- await new Future<Null>.delayed(new Duration(milliseconds: 500));
- }
+ // Scroll up
+ for (int i = 0; i < 5; i++) {
+ await driver.scroll(stockList, 0.0, 300.0, new Duration(milliseconds: 300));
+ await new Future<Null>.delayed(new Duration(milliseconds: 500));
+ }
+ });
+
+ // Usually the profile is saved to a file and then analyzed using
+ // chrom://tracing or a script. Both are out of scope for this little
+ // test, so all we do is check that we received something.
+ expect(profileJson, isNotNull);
});
});
}
diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart
index cac114e..bbb1286 100644
--- a/packages/flutter/lib/src/material/app_bar.dart
+++ b/packages/flutter/lib/src/material/app_bar.dart
@@ -42,9 +42,9 @@
AppBar copyWith({
Key key,
- Widget left,
- Widget center,
- List<Widget> right,
+ Widget leading,
+ Widget title,
+ List<Widget> actions,
WidgetBuilder flexibleSpace,
double foregroundOpacity,
int elevation,
@@ -54,9 +54,9 @@
}) {
return new AppBar(
key: key ?? this.key,
- leading: left ?? this.leading,
- title: center ?? this.title,
- actions: right ?? this.actions,
+ leading: leading ?? this.leading,
+ title: title ?? this.title,
+ actions: actions ?? this.actions,
flexibleSpace: flexibleSpace ?? this.flexibleSpace,
foregroundOpacity: foregroundOpacity ?? this.foregroundOpacity,
tabBar: tabBar ?? this.tabBar,
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index c0a6e0a..faf2cc8 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -406,10 +406,10 @@
if (appBar == null)
return null;
EdgeInsets appBarPadding = new EdgeInsets.only(top: padding.top);
- Widget left = appBar.leading;
- if (left == null) {
+ Widget leading = appBar.leading;
+ if (leading == null) {
if (config.drawer != null) {
- left = new IconButton(
+ leading = new IconButton(
icon: Icons.menu,
onPressed: openDrawer,
tooltip: 'Open navigation menu' // TODO(ianh): Figure out how to localize this string
@@ -417,7 +417,7 @@
} else {
_shouldShowBackArrow ??= Navigator.canPop(context);
if (_shouldShowBackArrow) {
- left = new IconButton(
+ leading = new IconButton(
icon: Icons.arrow_back,
onPressed: () => Navigator.pop(context),
tooltip: 'Back' // TODO(ianh): Figure out how to localize this string
@@ -429,7 +429,7 @@
elevation: elevation ?? appBar.elevation ?? 4,
padding: appBarPadding,
foregroundOpacity: foregroundOpacity,
- left: left
+ leading: leading
);
}
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index 300600e..3e88be0 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -1540,8 +1540,8 @@
/// A paragraph of rich text.
///
-/// This class is rarely used directly. Instead, consider using [Text], which
-/// integrates with [DefaultTextStyle].
+/// Consider using [Text], which integrates with [DefaultTextStyle], rather than
+/// this widget, which requires explicit styling.
class RichText extends LeafRenderObjectWidget {
RichText({ Key key, this.text }) : super(key: key) {
assert(text != null);
diff --git a/packages/flutter_driver/lib/src/driver.dart b/packages/flutter_driver/lib/src/driver.dart
index b58aeba..59fd3b3 100644
--- a/packages/flutter_driver/lib/src/driver.dart
+++ b/packages/flutter_driver/lib/src/driver.dart
@@ -29,6 +29,9 @@
FlutterDriver.connectedTo(this._serviceClient, this._appIsolate);
static const String _kFlutterExtensionMethod = 'ext.flutter_driver';
+ static const String _kStartTracingMethod = 'ext.flutter_startTracing';
+ static const String _kStopTracingMethod = 'ext.flutter_stopTracing';
+ static const String _kDownloadTraceDataMethod = 'ext.flutter_downloadTraceData';
static const Duration _kDefaultTimeout = const Duration(seconds: 5);
static const Duration _kDefaultPauseBetweenRetries = const Duration(milliseconds: 160);
@@ -205,6 +208,63 @@
return result.text;
}
+ /// Starts recording performance traces.
+ Future<Null> startTracing() async {
+ try {
+ await _appIsolate.invokeExtension(_kStartTracingMethod);
+ return null;
+ } catch(error, stackTrace) {
+ throw new DriverError(
+ 'Failed to start tracing due to remote error',
+ error,
+ stackTrace
+ );
+ }
+ }
+
+ /// Stops recording performance traces and downloads the trace profile.
+ // TODO(yjbanov): return structured data rather than raw JSON once we have a
+ // stable protocol to talk to.
+ Future<Map<String, dynamic>> stopTracingAndDownloadProfile() async {
+ Map<String, dynamic> stopResult =
+ await _appIsolate.invokeExtension(_kStopTracingMethod);
+ String traceFilePath = stopResult['trace_file_path'];
+
+ // Tracing data isn't available immediately as some of it is queued up in
+ // the event loop.
+ Stopwatch sw = new Stopwatch()..start();
+ while(sw.elapsed < const Duration(seconds: 30)) {
+ Map<String, dynamic> downloadResult =
+ await _appIsolate.invokeExtension(_kDownloadTraceDataMethod, <String, String>{
+ 'trace_file_path': traceFilePath,
+ });
+
+ if (downloadResult['success'] == false)
+ throw new DriverError('Failed to download trace file: $traceFilePath');
+ else if (downloadResult['status'] != 'not ready')
+ return downloadResult;
+
+ // Give the event loop a chance to flush the trace log
+ await new Future<Null>.delayed(const Duration(milliseconds: 200));
+ }
+ throw new DriverError(
+ 'Timed out waiting for tracing profile to become ready for download.'
+ );
+ }
+
+ /// Runs [action] and outputs a performance trace for it.
+ ///
+ /// Waits for the `Future` returned by [action] to complete prior to stopping
+ /// the trace.
+ ///
+ /// This is merely a convenience wrapper on top of [startTracing] and
+ /// [stopTracingAndDownloadProfile].
+ Future<Map<String, dynamic>> traceAction(Future<dynamic> action()) async {
+ await startTracing();
+ await action();
+ return stopTracingAndDownloadProfile();
+ }
+
/// Calls the [evaluator] repeatedly until the result of the evaluation
/// satisfies the [matcher].
///
diff --git a/packages/flutter_tools/lib/src/commands/analyze.dart b/packages/flutter_tools/lib/src/commands/analyze.dart
index b0a2ab3..011ba0b 100644
--- a/packages/flutter_tools/lib/src/commands/analyze.dart
+++ b/packages/flutter_tools/lib/src/commands/analyze.dart
@@ -332,8 +332,6 @@
'Analyzing [${mainFile.path}]...',
new RegExp('^\\[(hint|error)\\] Unused import \\(${mainFile.path},'),
new RegExp(r'^\[.+\] .+ \(.+/\.pub-cache/.+'),
- new RegExp('^\\[error\\] The argument type \'List<T>\' cannot be assigned to the parameter type \'List<.+>\''), // until we have generic methods, there's not much choice if you want to use map()
- new RegExp(r'^\[error\] Type check failed: .*\(dynamic\) is not of type'), // allow unchecked casts from dynamic
new RegExp('\\[warning\\] Missing concrete implementation of \'RenderObject\\.applyPaintTransform\''), // https://github.com/dart-lang/sdk/issues/25232
new RegExp(r'[0-9]+ (error|warning|hint|lint).+found\.'),
new RegExp(r'^$'),
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 9623642..fe02d31 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -22,7 +22,7 @@
const String _xcrunPath = '/usr/bin/xcrun';
/// Test device created by Flutter when no other device is available.
-const String _kFlutterTestDevice = 'flutter.test.device';
+const String _kFlutterTestDeviceSuffix = '(Flutter)';
class IOSSimulators extends PollingDeviceDiscovery {
IOSSimulators() : super('IOSSimulators');
@@ -97,7 +97,7 @@
}
SimDevice _createTestDevice() {
- String deviceType = _findSuitableDeviceType();
+ SimDeviceType deviceType = _findSuitableDeviceType();
if (deviceType == null) {
return null;
}
@@ -109,18 +109,19 @@
// Delete any old test devices
getDevices()
- .where((SimDevice d) => d.name == _kFlutterTestDevice)
+ .where((SimDevice d) => d.name.endsWith(_kFlutterTestDeviceSuffix))
.forEach(_deleteDevice);
// Create new device
- List<String> args = [_xcrunPath, 'simctl', 'create', _kFlutterTestDevice, deviceType, runtime];
+ String deviceName = '${deviceType.name} $_kFlutterTestDeviceSuffix';
+ List<String> args = [_xcrunPath, 'simctl', 'create', deviceName, deviceType.identifier, runtime];
printTrace(args.join(' '));
runCheckedSync(args);
- return getDevices().firstWhere((SimDevice d) => d.name == _kFlutterTestDevice);
+ return getDevices().firstWhere((SimDevice d) => d.name == deviceName);
}
- String _findSuitableDeviceType() {
+ SimDeviceType _findSuitableDeviceType() {
List<Map<String, dynamic>> allTypes = _list(SimControlListSection.devicetypes);
List<Map<String, dynamic>> usableTypes = allTypes
.where((Map<String, dynamic> info) => info['name'].startsWith('iPhone'))
@@ -136,7 +137,10 @@
);
}
- return usableTypes.first['identifier'];
+ return new SimDeviceType(
+ usableTypes.first['name'],
+ usableTypes.first['identifier']
+ );
}
String _findSuitableRuntime() {
@@ -300,6 +304,30 @@
static const SimControlListSection pairs = const SimControlListSection._('pairs');
}
+/// A simulated device type.
+///
+/// Simulated device types can be listed using the command
+/// `xcrun simctl list devicetypes`.
+class SimDeviceType {
+ SimDeviceType(this.name, this.identifier);
+
+ /// The name of the device type.
+ ///
+ /// Examples:
+ ///
+ /// "iPhone 6s"
+ /// "iPhone 6 Plus"
+ final String name;
+
+ /// The identifier of the device type.
+ ///
+ /// Examples:
+ ///
+ /// "com.apple.CoreSimulator.SimDeviceType.iPhone-6s"
+ /// "com.apple.CoreSimulator.SimDeviceType.iPhone-6-Plus"
+ final String identifier;
+}
+
class SimDevice {
SimDevice(this.category, this.data);
diff --git a/packages/flutter_tools/templates/create/lib/main.dart.tmpl b/packages/flutter_tools/templates/create/lib/main.dart.tmpl
index 3acf224..618bc1f 100644
--- a/packages/flutter_tools/templates/create/lib/main.dart.tmpl
+++ b/packages/flutter_tools/templates/create/lib/main.dart.tmpl
@@ -20,6 +20,7 @@
}
class FlutterDemo extends StatefulWidget {
+ @override
_FlutterDemoState createState() => new _FlutterDemoState();
}
@@ -32,6 +33,7 @@
});
}
+ @override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(