Avoid null manifests in FlutterProject (#20332)

diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 0ef82d0..2473479 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -203,18 +203,13 @@
 /// Overwrite local.properties in the specified Flutter project's Android
 /// sub-project, if needed.
 ///
-/// Throws tool exit, if `pubspec.yaml` is invalid.
-///
-/// If [requireSdk] is `true` this will fail with a tool exit if no Android Sdk
-/// is found.
+/// If [requireAndroidSdk] is true (the default) and no Android SDK is found,
+/// this will fail with a [ToolExit].
 Future<void> updateLocalProperties({
   @required FlutterProject project,
   BuildInfo buildInfo,
   bool requireAndroidSdk = true,
 }) async {
-  if (project.manifest == null) {
-    throwToolExit('Invalid `pubspec.yaml`');
-  }
   if (requireAndroidSdk && androidSdk == null) {
     throwToolExit('Unable to locate Android SDK. Please run `flutter doctor` for more details.');
   }
diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart
index 3cfb1e7..81a1e64 100644
--- a/packages/flutter_tools/lib/src/flutter_manifest.dart
+++ b/packages/flutter_tools/lib/src/flutter_manifest.dart
@@ -28,14 +28,6 @@
     return manifest;
   }
 
-  /// Returns a mock manifest with the given contents.
-  static FlutterManifest mock(Map<String, dynamic> contents) {
-    final FlutterManifest manifest = new FlutterManifest._();
-    manifest._descriptor = contents ?? const <String, dynamic>{};
-    manifest._flutterDescriptor = manifest._descriptor['flutter'] ?? const <String, dynamic>{};
-    return manifest;
-  }
-
   /// Returns null on invalid manifest. Returns empty manifest on missing file.
   static Future<FlutterManifest> createFromPath(String path) async {
     if (path == null || !fs.isFileSync(path))
@@ -78,8 +70,10 @@
   /// A map representation of the `flutter` section in the `pubspec.yaml` file.
   Map<String, dynamic> _flutterDescriptor;
 
+  /// True if the `pubspec.yaml` file does not exist.
   bool get isEmpty => _descriptor.isEmpty;
 
+  /// The string value of the top-level `name` property in the `pubspec.yaml` file.
   String get appName => _descriptor['name'] ?? '';
 
   /// The version String from the `pubspec.yaml` file.
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index 18b629b..c2aeaca 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -16,7 +16,6 @@
 import '../base/utils.dart';
 import '../build_info.dart';
 import '../cache.dart';
-import '../flutter_manifest.dart';
 import '../globals.dart';
 import '../project.dart';
 
@@ -59,20 +58,19 @@
 
   localsBuffer.writeln('SYMROOT=\${SOURCE_ROOT}/../${getIosBuildDirectory()}');
 
-  final FlutterManifest manifest = project.manifest;
-  if (!manifest.isModule) {
+  if (!project.isModule) {
     // For module projects we do not want to write the FLUTTER_FRAMEWORK_DIR
     // explicitly. Rather we rely on the xcode backend script and the Podfile
     // logic to derive it from FLUTTER_ROOT and FLUTTER_BUILD_MODE.
     localsBuffer.writeln('FLUTTER_FRAMEWORK_DIR=${flutterFrameworkDir(buildInfo.mode)}');
   }
 
-  final String buildName = buildInfo?.buildName ?? manifest.buildName;
+  final String buildName = buildInfo?.buildName ?? project.manifest.buildName;
   if (buildName != null) {
     localsBuffer.writeln('FLUTTER_BUILD_NAME=$buildName');
   }
 
-  final int buildNumber = buildInfo?.buildNumber ?? manifest.buildNumber;
+  final int buildNumber = buildInfo?.buildNumber ?? project.manifest.buildNumber;
   if (buildNumber != null) {
     localsBuffer.writeln('FLUTTER_BUILD_NUMBER=$buildNumber');
   }
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index b10192c..3c8743e 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -8,6 +8,7 @@
 import 'package:meta/meta.dart';
 
 import 'android/gradle.dart' as gradle;
+import 'base/common.dart';
 import 'base/file_system.dart';
 import 'build_info.dart';
 import 'bundle.dart' as bundle;
@@ -28,34 +29,39 @@
 /// cached.
 class FlutterProject {
   @visibleForTesting
-  FlutterProject(this.directory, this.manifest, this._exampleManifest);
+  FlutterProject(this.directory, this.manifest, this._exampleManifest)
+      : assert(directory != null),
+        assert(manifest != null),
+        assert(_exampleManifest != null);
 
-  /// Returns a future that completes with a FlutterProject view of the given directory.
+  /// Returns a future that completes with a [FlutterProject] view of the given directory
+  /// or a ToolExit error, if `pubspec.yaml` or `example/pubspec.yaml` is invalid.
   static Future<FlutterProject> fromDirectory(Directory directory) async {
-    final FlutterManifest manifest = await FlutterManifest.createFromPath(
+    assert(directory != null);
+    final FlutterManifest manifest = await _readManifest(
       directory.childFile(bundle.defaultManifestPath).path,
     );
-    final Directory exampleDirectory = _exampleDirectory(directory);
-    final FlutterManifest exampleManifest = await FlutterManifest.createFromPath(
-      exampleDirectory.childFile(bundle.defaultManifestPath).path,
+    final FlutterManifest exampleManifest = await _readManifest(
+      _exampleDirectory(directory).childFile(bundle.defaultManifestPath).path,
     );
     return new FlutterProject(directory, manifest, exampleManifest);
   }
 
-  /// Returns a future that completes with a FlutterProject view of the current directory.
+  /// Returns a future that completes with a [FlutterProject] view of the current directory.
+  /// or a ToolExit error, if `pubspec.yaml` or `example/pubspec.yaml` is invalid.
   static Future<FlutterProject> current() => fromDirectory(fs.currentDirectory);
 
-  /// Returns a future that completes with a FlutterProject view of the given directory.
+  /// Returns a future that completes with a [FlutterProject] view of the given directory.
+  /// or a ToolExit error, if `pubspec.yaml` or `example/pubspec.yaml` is invalid.
   static Future<FlutterProject> fromPath(String path) => fromDirectory(fs.directory(path));
 
   /// The location of this project.
   final Directory directory;
 
-  /// The manifest of this project, or null, if `pubspec.yaml` is invalid.
+  /// The manifest of this project.
   final FlutterManifest manifest;
 
-  /// The manifest of the example sub-project of this project, or null, if
-  /// `example/pubspec.yaml` is invalid.
+  /// The manifest of the example sub-project of this project.
   final FlutterManifest _exampleManifest;
 
   /// Asynchronously returns the organization names found in this project as
@@ -96,7 +102,8 @@
     FlutterManifest.empty(),
   );
 
-  bool get isModule => manifest != null && manifest.isModule;
+  /// True, if this project is a Flutter module.
+  bool get isModule => manifest.isModule;
 
   /// True, if this project has an example application.
   bool get hasExampleApp => _exampleDirectory(directory).existsSync();
@@ -104,6 +111,18 @@
   /// The directory that will contain the example if an example exists.
   static Directory _exampleDirectory(Directory directory) => directory.childDirectory('example');
 
+  /// Reads and validates the `pubspec.yaml` file at [path], asynchronously
+  /// returning a [FlutterManifest] representation of the contents.
+  ///
+  /// Completes with an empty [FlutterManifest], if the file does not exist.
+  /// Completes with a ToolExit on validation error.
+  static Future<FlutterManifest> _readManifest(String path) async {
+    final FlutterManifest manifest = await FlutterManifest.createFromPath(path);
+    if (manifest == null)
+      throwToolExit('Please correct the pubspec.yaml file at $path');
+    return manifest;
+  }
+
   /// Generates project files necessary to make Gradle builds work on Android
   /// and CocoaPods+Xcode work on iOS, for app and module projects only.
   Future<void> ensureReadyForPlatformSpecificTooling() async {