Add a `flutter create --list-samples` command (#28938)

* Add a `flutter create --list-samples` command

* Add some comments/docs

* Tweak text
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index e3d1d0e..c11fecc 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -95,6 +95,12 @@
       defaultsTo: null,
       valueHelp: 'id',
     );
+    argParser.addOption(
+      'list-samples',
+      help: 'Specifies a JSON output file for a listing of Flutter code samples '
+        'that can created with --sample.',
+      valueHelp: 'path',
+    );
     argParser.addFlag(
       'overwrite',
       negatable: true,
@@ -179,6 +185,11 @@
     return null;
   }
 
+  /// The hostname for the Flutter docs for the current channel.
+  String get _snippetsHost => FlutterVersion.instance.channel == 'stable'
+        ? 'docs.flutter.io'
+        : 'master-docs.flutter.io';
+
   Future<String> _fetchSampleFromServer(String sampleId) async {
     // Sanity check the sampleId
     if (sampleId.contains(RegExp(r'[^-\w\.]'))) {
@@ -186,14 +197,36 @@
         'documentation and try again.');
     }
 
-    final String host = FlutterVersion.instance.channel == 'stable'
-        ? 'docs.flutter.io'
-        : 'master-docs.flutter.io';
-    return utf8.decode(await fetchUrl(Uri.https(host, 'snippets/$sampleId.dart')));
+    return utf8.decode(await fetchUrl(Uri.https(_snippetsHost, 'snippets/$sampleId.dart')));
+  }
+
+  /// Fetches the samples index file from the Flutter docs website.
+  Future<String> _fetchSamplesIndexFromServer() async {
+    return utf8.decode(await fetchUrl(Uri.https(_snippetsHost, 'snippets/index.json')));
+  }
+
+  /// Fetches the samples index file from the server and writes it to
+  /// [outputFilePath].
+  Future<void> _writeSamplesJson(String outputFilePath) async {
+    try {
+      final File outputFile = fs.file(outputFilePath);
+      if (outputFile.existsSync()) {
+        throwToolExit('File "$outputFilePath" already exists', exitCode: 1);
+      }
+      outputFile.writeAsStringSync(await _fetchSamplesIndexFromServer());
+      printStatus('Wrote samples JSON to "$outputFilePath"');
+    } catch (e) {
+      throwToolExit('Failed to write samples JSON to "$outputFilePath": $e', exitCode: 2);
+    }
   }
 
   @override
   Future<FlutterCommandResult> runCommand() async {
+    if (argResults['list-samples'] != null) {
+      await _writeSamplesJson(argResults['list-samples']);
+      return null;
+    }
+
     if (argResults.rest.isEmpty)
       throwToolExit('No option specified for the output directory.\n$usage', exitCode: 2);
 
diff --git a/packages/flutter_tools/test/commands/create_test.dart b/packages/flutter_tools/test/commands/create_test.dart
index 8bf1604..4b91009 100644
--- a/packages/flutter_tools/test/commands/create_test.dart
+++ b/packages/flutter_tools/test/commands/create_test.dart
@@ -27,6 +27,10 @@
 final Map<Type, Generator> noColorTerminalOverride = <Type, Generator> {
   Platform: _kNoColorTerminalPlatform,
 };
+const String samplesIndexJson = '''[
+  { "id": "sample1" },
+  { "id": "sample2" }
+]''';
 
 void main() {
   Directory tempDir;
@@ -888,6 +892,41 @@
   }, timeout: allowForRemotePubInvocation, overrides: <Type, Generator>{
     HttpClientFactory: () => () => MockHttpClient(200, result: 'void main() {}'),
   });
+
+  testUsingContext('can write samples index to disk', () async {
+    final String outputFile = fs.path.join(tempDir.path, 'flutter_samples.json');
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+    final List<String> args = <String>[
+      'create',
+      '--list-samples',
+      outputFile
+    ];
+
+    await runner.run(args);
+    final File expectedFile = fs.file(outputFile);
+    expect(expectedFile.existsSync(), isTrue);
+    expect(expectedFile.readAsStringSync(), equals(samplesIndexJson));
+  }, overrides: <Type, Generator>{
+    HttpClientFactory: () =>
+        () => MockHttpClient(200, result: samplesIndexJson),
+  });
+  testUsingContext('provides an error to the user if samples json download fails', () async {
+    final String outputFile = fs.path.join(tempDir.path, 'flutter_samples.json');
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+    final List<String> args = <String>[
+      'create',
+      '--list-samples',
+      outputFile
+    ];
+
+    await expectLater(runner.run(args), throwsToolExit(exitCode: 2, message: 'Failed to write samples'));
+    expect(fs.file(outputFile).existsSync(), isFalse);
+  }, overrides: <Type, Generator>{
+    HttpClientFactory: () =>
+        () => MockHttpClient(404, result: 'not found'),
+  });
 }
 
 Future<void> _createProject(