Revert "Revert "Use FlutterProject to locate files (#18913)" (#19409)"  (#19456)

With a fix of a path being printed relative instead of absolute.
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 65fb6ab..c69d947 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -21,6 +21,7 @@
 import '../build_info.dart';
 import '../device.dart';
 import '../globals.dart';
+import '../project.dart';
 import '../protocol_discovery.dart';
 
 import 'adb.dart';
@@ -241,7 +242,7 @@
 
   String _getSourceSha1(ApplicationPackage app) {
     final AndroidApk apk = app;
-    final File shaFile = fs.file('${apk.apkPath}.sha1');
+    final File shaFile = fs.file('${apk.file.path}.sha1');
     return shaFile.existsSync() ? shaFile.readAsStringSync() : '';
   }
 
@@ -269,16 +270,16 @@
   @override
   Future<bool> installApp(ApplicationPackage app) async {
     final AndroidApk apk = app;
-    if (!fs.isFileSync(apk.apkPath)) {
-      printError('"${apk.apkPath}" does not exist.');
+    if (!apk.file.existsSync()) {
+      printError('"${fs.path.relative(apk.file.path)}" does not exist.');
       return false;
     }
 
     if (!await _checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion())
       return false;
 
-    final Status status = logger.startProgress('Installing ${apk.apkPath}...', expectSlowOperation: true);
-    final RunResult installResult = await runAsync(adbCommandForDevice(<String>['install', '-t', '-r', apk.apkPath]));
+    final Status status = logger.startProgress('Installing ${fs.path.relative(apk.file.path)}...', expectSlowOperation: true);
+    final RunResult installResult = await runAsync(adbCommandForDevice(<String>['install', '-t', '-r', apk.file.path]));
     status.stop();
     // Some versions of adb exit with exit code 0 even on failure :(
     // Parsing the output to check for failures.
@@ -374,12 +375,13 @@
     if (!prebuiltApplication) {
       printTrace('Building APK');
       await buildApk(
+          project: new FlutterProject(fs.currentDirectory),
           target: mainPath,
           buildInfo: buildInfo,
       );
       // Package has been built, so we can get the updated application ID and
       // activity name from the .apk.
-      package = await AndroidApk.fromCurrentDirectory();
+      package = await AndroidApk.fromAndroidProject(new FlutterProject(fs.currentDirectory).android);
     }
 
     printTrace("Stopping app '${package.name}' on $name.");
diff --git a/packages/flutter_tools/lib/src/android/apk.dart b/packages/flutter_tools/lib/src/android/apk.dart
index 4a2af8e..57e7f29 100644
--- a/packages/flutter_tools/lib/src/android/apk.dart
+++ b/packages/flutter_tools/lib/src/android/apk.dart
@@ -4,17 +4,22 @@
 
 import 'dart:async';
 
+import 'package:meta/meta.dart';
+
 import '../base/common.dart';
 import '../build_info.dart';
 import '../globals.dart';
+import '../project.dart';
+
 import 'android_sdk.dart';
 import 'gradle.dart';
 
 Future<Null> buildApk({
-  String target,
+  @required FlutterProject project,
+  @required String target,
   BuildInfo buildInfo = BuildInfo.debug
 }) async {
-  if (!isProjectUsingGradle()) {
+  if (!project.android.isUsingGradle()) {
     throwToolExit(
         'The build process for Android has changed, and the current project configuration\n'
             'is no longer valid. Please consult\n\n'
@@ -33,5 +38,9 @@
     throwToolExit('Try re-installing or updating your Android SDK.');
   }
 
-  return buildGradleProject(buildInfo, target);
+  return buildGradleProject(
+    project: project,
+    buildInfo: buildInfo,
+    target: target,
+  );
 }
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 833ae58..c7b1e25 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -4,6 +4,8 @@
 
 import 'dart:async';
 
+import 'package:meta/meta.dart';
+
 import '../android/android_sdk.dart';
 import '../artifacts.dart';
 import '../base/common.dart';
@@ -14,16 +16,13 @@
 import '../base/process.dart';
 import '../base/utils.dart';
 import '../build_info.dart';
-import '../bundle.dart' as bundle;
 import '../cache.dart';
 import '../flutter_manifest.dart';
 import '../globals.dart';
+import '../project.dart';
 import 'android_sdk.dart';
 import 'android_studio.dart';
 
-const String gradleManifestPath = 'android/app/src/main/AndroidManifest.xml';
-const String gradleAppOutV1 = 'android/app/build/outputs/apk/app-debug.apk';
-const String gradleAppOutDirV1 = 'android/app/build/outputs/apk';
 const String gradleVersion = '4.1';
 final RegExp _assembleTaskPattern = new RegExp(r'assemble([^:]+): task ');
 
@@ -45,9 +44,6 @@
   r'|If you are using NDK, verify the ndk.dir is set to a valid NDK directory.  It is currently set to .*)');
 
 
-bool isProjectUsingGradle() {
-  return fs.isFileSync('android/build.gradle');
-}
 
 FlutterPluginVersion get flutterPluginVersion {
   final File plugin = fs.file('android/buildSrc/src/main/groovy/FlutterPlugin.groovy');
@@ -69,18 +65,17 @@
   return FlutterPluginVersion.none;
 }
 
-/// Returns the path to the apk file created by [buildGradleProject], relative
-/// to current directory.
-Future<String> getGradleAppOut() async {
+/// Returns the apk file created by [buildGradleProject]
+Future<File> getGradleAppOut(AndroidProject androidProject) async {
   switch (flutterPluginVersion) {
     case FlutterPluginVersion.none:
       // Fall through. Pretend we're v1, and just go with it.
     case FlutterPluginVersion.v1:
-      return gradleAppOutV1;
+      return androidProject.gradleAppOutV1File;
     case FlutterPluginVersion.managed:
       // Fall through. The managed plugin matches plugin v2 for now.
     case FlutterPluginVersion.v2:
-      return fs.path.relative(fs.path.join((await _gradleProject()).apkDirectory, 'app.apk'));
+      return fs.file((await _gradleProject()).apkDirectory.childFile('app.apk'));
   }
   return null;
 }
@@ -94,12 +89,13 @@
 // of calculating the app properties using Gradle. This may take minutes.
 Future<GradleProject> _readGradleProject() async {
   final String gradle = await _ensureGradle();
-  await updateLocalProperties();
+  final FlutterProject flutterProject =  new FlutterProject(fs.currentDirectory);
+  await updateLocalProperties(project: flutterProject);
   try {
     final Status status = logger.startProgress('Resolving dependencies...', expectSlowOperation: true);
     final RunResult runResult = await runCheckedAsync(
       <String>[gradle, 'app:properties'],
-      workingDirectory: 'android',
+      workingDirectory: flutterProject.android.directory.path,
       environment: _gradleEnv,
     );
     final String properties = runResult.stdout.trim();
@@ -117,7 +113,7 @@
     }
   }
   // Fall back to the default
-  return new GradleProject(<String>['debug', 'profile', 'release'], <String>[], gradleAppOutDirV1);
+  return new GradleProject(<String>['debug', 'profile', 'release'], <String>[], flutterProject.android.gradleAppOutV1Directory);
 }
 
 void handleKnownGradleExceptions(String exceptionString) {
@@ -199,28 +195,19 @@
 /// Overwrite android/local.properties in the specified Flutter project, if needed.
 ///
 /// Throws, if `pubspec.yaml` or Android SDK cannot be located.
-Future<void> updateLocalProperties({String projectPath, BuildInfo buildInfo}) async {
-  final Directory android = (projectPath == null)
-      ? fs.directory('android')
-      : fs.directory(fs.path.join(projectPath, 'android'));
-  final String flutterManifest = (projectPath == null)
-      ? fs.path.join(bundle.defaultManifestPath)
-      : fs.path.join(projectPath, bundle.defaultManifestPath);
-  if (androidSdk == null) {
+///
+/// If [requireSdk] is `true` this will fail with a tool-exit if no Android Sdk
+/// is found.
+Future<void> updateLocalProperties({
+  @required FlutterProject project,
+  BuildInfo buildInfo,
+  bool requireAndroidSdk = true,
+}) async {
+  if (requireAndroidSdk && androidSdk == null) {
     throwToolExit('Unable to locate Android SDK. Please run `flutter doctor` for more details.');
   }
-  FlutterManifest manifest;
-  try {
-    manifest = await FlutterManifest.createFromPath(flutterManifest);
-  } catch (error) {
-    throwToolExit('Failed to load pubspec.yaml: $error');
-  }
-  updateLocalPropertiesSync(android, manifest, buildInfo);
-}
 
-/// Overwrite local.properties in the specified directory, if needed.
-void updateLocalPropertiesSync(Directory android, FlutterManifest manifest, [BuildInfo buildInfo]) {
-  final File localProperties = android.childFile('local.properties');
+  final File localProperties = await project.androidLocalPropertiesFile;
   bool changed = false;
 
   SettingsFile settings;
@@ -238,6 +225,8 @@
     }
   }
 
+  final FlutterManifest manifest = await project.manifest;
+
   if (androidSdk != null)
     changeIfNecessary('sdk.dir', escapePath(androidSdk.directory));
   changeIfNecessary('flutter.sdk', escapePath(Cache.flutterRoot));
@@ -254,7 +243,11 @@
     settings.writeContents(localProperties);
 }
 
-Future<Null> buildGradleProject(BuildInfo buildInfo, String target) async {
+Future<Null> buildGradleProject({
+  @required FlutterProject project,
+  @required BuildInfo buildInfo,
+  @required String target,
+}) async {
   // Update the local.properties file with the build mode, version name and code.
   // FlutterPlugin v1 reads local.properties to determine build mode. Plugin v2
   // uses the standard Android way to determine what to build, but we still
@@ -263,7 +256,7 @@
   // and can be overwritten with flutter build command.
   // The default Gradle script reads the version name and number
   // from the local.properties file.
-  await updateLocalProperties(buildInfo: buildInfo);
+  await updateLocalProperties(project: project, buildInfo: buildInfo);
 
   final String gradle = await _ensureGradle();
 
@@ -271,7 +264,7 @@
     case FlutterPluginVersion.none:
       // Fall through. Pretend it's v1, and just go for it.
     case FlutterPluginVersion.v1:
-      return _buildGradleProjectV1(gradle);
+      return _buildGradleProjectV1(project, gradle);
     case FlutterPluginVersion.managed:
       // Fall through. Managed plugin builds the same way as plugin v2.
     case FlutterPluginVersion.v2:
@@ -279,7 +272,7 @@
   }
 }
 
-Future<Null> _buildGradleProjectV1(String gradle) async {
+Future<Null> _buildGradleProjectV1(FlutterProject project, String gradle) async {
   // Run 'gradlew build'.
   final Status status = logger.startProgress('Running \'gradlew build\'...', expectSlowOperation: true);
   final int exitCode = await runCommandAndStreamOutput(
@@ -293,7 +286,7 @@
   if (exitCode != 0)
     throwToolExit('Gradle build failed: $exitCode', exitCode: exitCode);
 
-  printStatus('Built $gradleAppOutV1.');
+  printStatus('Built ${fs.path.relative(project.android.gradleAppOutV1File.path)}.');
 }
 
 Future<Null> _buildGradleProjectV2(String gradle, BuildInfo buildInfo, String target) async {
@@ -371,10 +364,10 @@
   if (apkFile == null)
     throwToolExit('Gradle build failed to produce an Android package.');
   // Copy the APK to app.apk, so `flutter run`, `flutter install`, etc. can find it.
-  apkFile.copySync(fs.path.join(project.apkDirectory, 'app.apk'));
+  apkFile.copySync(project.apkDirectory.childFile('app.apk').path);
 
   printTrace('calculateSha: ${project.apkDirectory}/app.apk');
-  final File apkShaFile = fs.file(fs.path.join(project.apkDirectory, 'app.apk.sha1'));
+  final File apkShaFile = project.apkDirectory.childFile('app.apk.sha1');
   apkShaFile.writeAsStringSync(calculateSha(apkFile));
 
   String appSize;
@@ -390,15 +383,15 @@
   final String apkFileName = project.apkFileFor(buildInfo);
   if (apkFileName == null)
     return null;
-  File apkFile = fs.file(fs.path.join(project.apkDirectory, apkFileName));
+  File apkFile = fs.file(fs.path.join(project.apkDirectory.path, apkFileName));
   if (apkFile.existsSync())
     return apkFile;
-  apkFile = fs.file(fs.path.join(project.apkDirectory, buildInfo.modeName, apkFileName));
+  apkFile = fs.file(fs.path.join(project.apkDirectory.path, buildInfo.modeName, apkFileName));
   if (apkFile.existsSync())
     return apkFile;
   if (buildInfo.flavor != null) {
     // Android Studio Gradle plugin v3 adds flavor to path.
-    apkFile = fs.file(fs.path.join(project.apkDirectory, buildInfo.flavor, buildInfo.modeName, apkFileName));
+    apkFile = fs.file(fs.path.join(project.apkDirectory.path, buildInfo.flavor, buildInfo.modeName, apkFileName));
     if (apkFile.existsSync())
       return apkFile;
   }
@@ -453,13 +446,13 @@
     return new GradleProject(
       buildTypes.toList(),
       productFlavors.toList(),
-      fs.path.normalize(fs.path.join(buildDir, 'outputs', 'apk')),
+      fs.directory(fs.path.join(buildDir, 'outputs', 'apk')),
     );
   }
 
   final List<String> buildTypes;
   final List<String> productFlavors;
-  final String apkDirectory;
+  final Directory apkDirectory;
 
   String _buildTypeFor(BuildInfo buildInfo) {
     if (buildTypes.contains(buildInfo.modeName))
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index ff52975..b1dbdaa 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -18,6 +18,7 @@
 import 'ios/ios_workflow.dart';
 import 'ios/plist_utils.dart' as plist;
 import 'ios/xcodeproj.dart';
+import 'project.dart';
 import 'tester/flutter_tester.dart';
 
 abstract class ApplicationPackage {
@@ -31,7 +32,7 @@
 
   String get displayName => name;
 
-  String get packagePath => null;
+  File get packagesFile => null;
 
   @override
   String toString() => displayName;
@@ -39,21 +40,21 @@
 
 class AndroidApk extends ApplicationPackage {
   /// Path to the actual apk file.
-  final String apkPath;
+  final File file;
 
   /// The path to the activity that should be launched.
   final String launchActivity;
 
   AndroidApk({
     String id,
-    @required this.apkPath,
+    @required this.file,
     @required this.launchActivity
-  }) : assert(apkPath != null),
+  }) : assert(file != null),
        assert(launchActivity != null),
        super(id: id);
 
   /// Creates a new AndroidApk from an existing APK.
-  factory AndroidApk.fromApk(String applicationBinary) {
+  factory AndroidApk.fromApk(File apk) {
     final String aaptPath = androidSdk?.latestVersion?.aaptPath;
     if (aaptPath == null) {
       printError('Unable to locate the Android SDK; please run \'flutter doctor\'.');
@@ -64,7 +65,7 @@
        aaptPath,
       'dump',
       'xmltree',
-      applicationBinary,
+      apk.path,
       'AndroidManifest.xml',
     ];
 
@@ -72,47 +73,46 @@
         .parseFromXmlDump(runCheckedSync(aaptArgs));
 
     if (data == null) {
-      printError('Unable to read manifest info from $applicationBinary.');
+      printError('Unable to read manifest info from ${apk.path}.');
       return null;
     }
 
     if (data.packageName == null || data.launchableActivityName == null) {
-      printError('Unable to read manifest info from $applicationBinary.');
+      printError('Unable to read manifest info from ${apk.path}.');
       return null;
     }
 
     return new AndroidApk(
       id: data.packageName,
-      apkPath: applicationBinary,
+      file: apk,
       launchActivity: '${data.packageName}/${data.launchableActivityName}'
     );
   }
 
   /// Creates a new AndroidApk based on the information in the Android manifest.
-  static Future<AndroidApk> fromCurrentDirectory() async {
-    String manifestPath;
-    String apkPath;
+  static Future<AndroidApk> fromAndroidProject(AndroidProject androidProject) async {
+    File apkFile;
 
-    if (isProjectUsingGradle()) {
-      apkPath = await getGradleAppOut();
-      if (fs.file(apkPath).existsSync()) {
+    if (androidProject.isUsingGradle()) {
+      apkFile = await getGradleAppOut(androidProject);
+      if (apkFile.existsSync()) {
         // Grab information from the .apk. The gradle build script might alter
         // the application Id, so we need to look at what was actually built.
-        return new AndroidApk.fromApk(apkPath);
+        return new AndroidApk.fromApk(apkFile);
       }
       // The .apk hasn't been built yet, so we work with what we have. The run
       // command will grab a new AndroidApk after building, to get the updated
       // IDs.
-      manifestPath = gradleManifestPath;
     } else {
-      manifestPath = fs.path.join('android', 'AndroidManifest.xml');
-      apkPath = fs.path.join(getAndroidBuildDirectory(), 'app.apk');
+      apkFile = fs.file(fs.path.join(getAndroidBuildDirectory(), 'app.apk'));
     }
 
-    if (!fs.isFileSync(manifestPath))
+    final File manifest = androidProject.gradleManifestFile;
+
+    if (!manifest.existsSync())
       return null;
 
-    final String manifestString = fs.file(manifestPath).readAsStringSync();
+    final String manifestString = manifest.readAsStringSync();
     final xml.XmlDocument document = xml.parse(manifestString);
 
     final Iterable<xml.XmlElement> manifests = document.findElements('manifest');
@@ -138,16 +138,16 @@
 
     return new AndroidApk(
       id: packageId,
-      apkPath: apkPath,
+      file: apkFile,
       launchActivity: launchActivity
     );
   }
 
   @override
-  String get packagePath => apkPath;
+  File get packagesFile => file;
 
   @override
-  String get name => fs.path.basename(apkPath);
+  String get name => file.basename;
 }
 
 /// Tests whether a [FileSystemEntity] is an iOS bundle directory
@@ -158,18 +158,18 @@
   IOSApp({@required String projectBundleId}) : super(id: projectBundleId);
 
   /// Creates a new IOSApp from an existing app bundle or IPA.
-  factory IOSApp.fromPrebuiltApp(String applicationBinary) {
-    final FileSystemEntityType entityType = fs.typeSync(applicationBinary);
+  factory IOSApp.fromPrebuiltApp(FileSystemEntity applicationBinary) {
+    final FileSystemEntityType entityType = fs.typeSync(applicationBinary.path);
     if (entityType == FileSystemEntityType.notFound) {
       printError(
-          'File "$applicationBinary" does not exist. Use an app bundle or an ipa.');
+          'File "${applicationBinary.path}" does not exist. Use an app bundle or an ipa.');
       return null;
     }
     Directory bundleDir;
     if (entityType == FileSystemEntityType.directory) {
       final Directory directory = fs.directory(applicationBinary);
       if (!_isBundleDirectory(directory)) {
-        printError('Folder "$applicationBinary" is not an app bundle.');
+        printError('Folder "${applicationBinary.path}" is not an app bundle.');
         return null;
       }
       bundleDir = fs.directory(applicationBinary);
@@ -305,16 +305,16 @@
   String get _bundlePath => bundleDir.path;
 }
 
-Future<ApplicationPackage> getApplicationPackageForPlatform(TargetPlatform platform, {
-  String applicationBinary
-}) async {
+Future<ApplicationPackage> getApplicationPackageForPlatform(
+    TargetPlatform platform,
+    {File applicationBinary}) async {
   switch (platform) {
     case TargetPlatform.android_arm:
     case TargetPlatform.android_arm64:
     case TargetPlatform.android_x64:
     case TargetPlatform.android_x86:
       return applicationBinary == null
-          ? await AndroidApk.fromCurrentDirectory()
+          ? await AndroidApk.fromAndroidProject(new FlutterProject(fs.currentDirectory).android)
           : new AndroidApk.fromApk(applicationBinary);
     case TargetPlatform.ios:
       return applicationBinary == null
@@ -344,7 +344,7 @@
       case TargetPlatform.android_arm64:
       case TargetPlatform.android_x64:
       case TargetPlatform.android_x86:
-        android ??= await AndroidApk.fromCurrentDirectory();
+        android ??= await AndroidApk.fromAndroidProject(new FlutterProject(fs.currentDirectory).android);
         return android;
       case TargetPlatform.ios:
         iOS ??= new IOSApp.fromCurrentDirectory();
diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart
index e6e2cc5..3dda93a 100644
--- a/packages/flutter_tools/lib/src/commands/build_apk.dart
+++ b/packages/flutter_tools/lib/src/commands/build_apk.dart
@@ -5,6 +5,8 @@
 import 'dart:async';
 
 import '../android/apk.dart';
+import '../base/file_system.dart';
+import '../project.dart';
 import 'build.dart';
 
 class BuildApkCommand extends BuildSubCommand {
@@ -44,6 +46,6 @@
   @override
   Future<Null> runCommand() async {
     await super.runCommand();
-    await buildApk(buildInfo: getBuildInfo(), target: targetFile);
+    await buildApk(project: new FlutterProject(fs.currentDirectory),target: targetFile, buildInfo: getBuildInfo());
   }
 }
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index 0c69f18..7c97cf3 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -168,20 +168,19 @@
 
     printStatus('Creating project ${fs.path.relative(dirPath)}...');
     int generatedFileCount = 0;
-    String appPath = dirPath;
+    final FlutterProject project = new FlutterProject.fromPath(dirPath);
     switch (template) {
       case 'app':
-        generatedFileCount += await _generateApp(dirPath, templateContext);
+        generatedFileCount += await _generateApp(project, templateContext);
         break;
       case 'module':
-        generatedFileCount += await _generateModule(dirPath, templateContext);
+        generatedFileCount += await _generateModule(project, templateContext);
         break;
       case 'package':
-        generatedFileCount += await _generatePackage(dirPath, templateContext);
+        generatedFileCount += await _generatePackage(project, templateContext);
         break;
       case 'plugin':
-        appPath = fs.path.join(dirPath, 'example');
-        generatedFileCount += await _generatePlugin(dirPath, appPath, templateContext);
+        generatedFileCount += await _generatePlugin(project, templateContext);
         break;
     }
     printStatus('Wrote $generatedFileCount files.');
@@ -194,7 +193,8 @@
       printStatus('Your module code is in lib/main.dart in the $relativePath directory.');
     } else {
       // Run doctor; tell the user the next steps.
-      final String relativeAppPath = fs.path.relative(appPath);
+      final FlutterProject app = project.hasExampleApp ? project.example : project;
+      final String relativeAppPath = fs.path.relative(app.directory.path);
       final String relativePluginPath = fs.path.relative(dirPath);
       if (doctor.canLaunchAnything) {
         // Let them know a summary of the state of their tooling.
@@ -233,60 +233,59 @@
     }
   }
 
-  Future<int> _generateModule(String path, Map<String, dynamic> templateContext) async {
+  Future<int> _generateModule(FlutterProject project, Map<String, dynamic> templateContext) async {
     int generatedCount = 0;
     final String description = argResults.wasParsed('description')
         ? argResults['description']
         : 'A new flutter module project.';
     templateContext['description'] = description;
-    generatedCount += _renderTemplate(fs.path.join('module', 'common'), path, templateContext);
+    generatedCount += _renderTemplate(fs.path.join('module', 'common'), project.directory, templateContext);
     if (argResults['pub']) {
       await pubGet(
         context: PubContext.create,
-        directory: path,
+        directory: project.directory.path,
         offline: argResults['offline'],
       );
-      final FlutterProject project = new FlutterProject.fromPath(path);
       await project.ensureReadyForPlatformSpecificTooling();
     }
     return generatedCount;
   }
 
-  Future<int> _generatePackage(String dirPath, Map<String, dynamic> templateContext) async {
+  Future<int> _generatePackage(FlutterProject project, Map<String, dynamic> templateContext) async {
     int generatedCount = 0;
     final String description = argResults.wasParsed('description')
        ? argResults['description']
        : 'A new flutter package project.';
     templateContext['description'] = description;
-    generatedCount += _renderTemplate('package', dirPath, templateContext);
+    generatedCount += _renderTemplate('package', project.directory, templateContext);
 
     if (argResults['pub']) {
       await pubGet(
         context: PubContext.createPackage,
-        directory: dirPath,
+        directory: project.directory.path,
         offline: argResults['offline'],
       );
     }
     return generatedCount;
   }
 
-  Future<int> _generatePlugin(String dirPath, String appPath, Map<String, dynamic> templateContext) async {
+  Future<int> _generatePlugin(FlutterProject project, Map<String, dynamic> templateContext) async {
     int generatedCount = 0;
     final String description = argResults.wasParsed('description')
         ? argResults['description']
         : 'A new flutter plugin project.';
     templateContext['description'] = description;
-    generatedCount += _renderTemplate('plugin', dirPath, templateContext);
+    generatedCount += _renderTemplate('plugin', project.directory, templateContext);
 
     if (argResults['pub']) {
       await pubGet(
         context: PubContext.createPlugin,
-        directory: dirPath,
+        directory: project.directory.path,
         offline: argResults['offline'],
       );
     }
     if (android_sdk.androidSdk != null)
-      await gradle.updateLocalProperties(projectPath: dirPath);
+      await gradle.updateLocalProperties(project: project);
 
     final String projectName = templateContext['projectName'];
     final String organization = templateContext['organization'];
@@ -299,27 +298,27 @@
     templateContext['pluginProjectName'] = projectName;
     templateContext['androidPluginIdentifier'] = androidPluginIdentifier;
 
-    generatedCount += await _generateApp(appPath, templateContext);
+    generatedCount += await _generateApp(project.example, templateContext);
     return generatedCount;
   }
 
-  Future<int> _generateApp(String projectPath, Map<String, dynamic> templateContext) async {
+  Future<int> _generateApp(FlutterProject project, Map<String, dynamic> templateContext) async {
     int generatedCount = 0;
-    generatedCount += _renderTemplate('create', projectPath, templateContext);
-    generatedCount += _injectGradleWrapper(projectPath);
+    generatedCount += _renderTemplate('create', project.directory, templateContext);
+    generatedCount += _injectGradleWrapper(project);
 
     if (argResults['with-driver-test']) {
-      final String testPath = fs.path.join(projectPath, 'test_driver');
-      generatedCount += _renderTemplate('driver', testPath, templateContext);
+      final Directory testDirectory = project.directory.childDirectory('test_driver');
+      generatedCount += _renderTemplate('driver', testDirectory, templateContext);
     }
 
     if (argResults['pub']) {
-      await pubGet(context: PubContext.create, directory: projectPath, offline: argResults['offline']);
-      await new FlutterProject.fromPath(projectPath).ensureReadyForPlatformSpecificTooling();
+      await pubGet(context: PubContext.create, directory: project.directory.path, offline: argResults['offline']);
+      await project.ensureReadyForPlatformSpecificTooling();
     }
 
     if (android_sdk.androidSdk != null)
-      await gradle.updateLocalProperties(projectPath: projectPath);
+      await gradle.updateLocalProperties(project: project);
 
     return generatedCount;
   }
@@ -362,16 +361,16 @@
     };
   }
 
-  int _renderTemplate(String templateName, String dirPath, Map<String, dynamic> context) {
+  int _renderTemplate(String templateName, Directory directory, Map<String, dynamic> context) {
     final Template template = new Template.fromName(templateName);
-    return template.render(fs.directory(dirPath), context, overwriteExisting: false);
+    return template.render(directory, context, overwriteExisting: false);
   }
 
-  int _injectGradleWrapper(String projectDir) {
+  int _injectGradleWrapper(FlutterProject project) {
     int filesCreated = 0;
     copyDirectorySync(
       cache.getArtifactDirectory('gradle_wrapper'),
-      fs.directory(fs.path.join(projectDir, 'android')),
+      project.android.directory,
       (File sourceFile, File destinationFile) {
         filesCreated++;
         final String modes = sourceFile.statSync().modeString();
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index 140b04b..0d1a3d5 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -327,7 +327,7 @@
   Future<AppInstance> startApp(
     Device device, String projectDirectory, String target, String route,
     DebuggingOptions options, bool enableHotReload, {
-    String applicationBinary,
+    File applicationBinary,
     @required bool trackWidgetCreation,
     String projectRootPath,
     String packagesFilePath,
diff --git a/packages/flutter_tools/lib/src/commands/inject_plugins.dart b/packages/flutter_tools/lib/src/commands/inject_plugins.dart
index 832b642..6a1bb94 100644
--- a/packages/flutter_tools/lib/src/commands/inject_plugins.dart
+++ b/packages/flutter_tools/lib/src/commands/inject_plugins.dart
@@ -5,9 +5,9 @@
 import 'dart:async';
 
 import '../base/file_system.dart';
-import '../flutter_manifest.dart';
 import '../globals.dart';
 import '../plugins.dart';
+import '../project.dart';
 import '../runner/flutter_command.dart';
 
 class InjectPluginsCommand extends FlutterCommand {
@@ -26,10 +26,9 @@
 
   @override
   Future<Null> runCommand() async {
-    final String projectPath = fs.currentDirectory.path;
-    final FlutterManifest manifest = await FlutterManifest.createFromPath(projectPath);
-    injectPlugins(projectPath: projectPath, manifest: manifest);
-    final bool result = hasPlugins();
+    final FlutterProject project = new FlutterProject(fs.currentDirectory);
+    await injectPlugins(project);
+    final bool result = hasPlugins(project);
     if (result) {
       printStatus('GeneratedPluginRegistrants successfully written.');
     } else {
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index eafdd1e..730be16 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -289,10 +289,13 @@
           notifyingLogger: new NotifyingLogger(), logToStdout: true);
       AppInstance app;
       try {
+        final String applicationBinaryPath = argResults['use-application-binary'];
         app = await daemon.appDomain.startApp(
           devices.first, fs.currentDirectory.path, targetFile, route,
           _createDebuggingOptions(), hotMode,
-          applicationBinary: argResults['use-application-binary'],
+          applicationBinary: applicationBinaryPath == null
+              ? null
+              : fs.file(applicationBinaryPath),
           trackWidgetCreation: argResults['track-widget-creation'],
           projectRootPath: argResults['project-root'],
           packagesFilePath: globalResults['packages'],
diff --git a/packages/flutter_tools/lib/src/ios/cocoapods.dart b/packages/flutter_tools/lib/src/ios/cocoapods.dart
index 9904812..d6dc48c 100644
--- a/packages/flutter_tools/lib/src/ios/cocoapods.dart
+++ b/packages/flutter_tools/lib/src/ios/cocoapods.dart
@@ -15,8 +15,8 @@
 import '../base/process_manager.dart';
 import '../base/version.dart';
 import '../cache.dart';
-import '../flutter_manifest.dart';
 import '../globals.dart';
+import '../project.dart';
 import 'xcodeproj.dart';
 
 const String noCocoaPodsConsequence = '''
@@ -81,18 +81,18 @@
   Future<bool> get isCocoaPodsInitialized => fs.isDirectory(fs.path.join(homeDirPath, '.cocoapods', 'repos', 'master'));
 
   Future<bool> processPods({
-    @required Directory appIosDirectory,
+    @required IosProject iosProject,
     // For backward compatibility with previously created Podfile only.
     @required String iosEngineDir,
     bool isSwift = false,
     bool dependenciesChanged = true,
   }) async {
-    if (!(await appIosDirectory.childFile('Podfile').exists())) {
+    if (!(await iosProject.podfile.exists())) {
       throwToolExit('Podfile missing');
     }
     if (await _checkPodCondition()) {
-      if (_shouldRunPodInstall(appIosDirectory, dependenciesChanged)) {
-        await _runPodInstall(appIosDirectory, iosEngineDir);
+      if (_shouldRunPodInstall(iosProject, dependenciesChanged)) {
+        await _runPodInstall(iosProject, iosEngineDir);
         return true;
       }
     }
@@ -151,18 +151,18 @@
   /// Ensures the `ios` sub-project of the Flutter project at [appDirectory]
   /// contains a suitable `Podfile` and that its `Flutter/Xxx.xcconfig` files
   /// include pods configuration.
-  void setupPodfile(String appDirectory, FlutterManifest manifest) {
+  void setupPodfile(IosProject iosProject) {
     if (!xcodeProjectInterpreter.isInstalled) {
       // Don't do anything for iOS when host platform doesn't support it.
       return;
     }
-    if (!fs.directory(fs.path.join(appDirectory, 'ios')).existsSync()) {
+    if (!iosProject.directory.existsSync()) {
       return;
     }
-    final String podfilePath = fs.path.join(appDirectory, 'ios', 'Podfile');
-    if (!fs.file(podfilePath).existsSync()) {
+    final File podfile = iosProject.podfile;
+    if (!podfile.existsSync()) {
       final bool isSwift = xcodeProjectInterpreter.getBuildSettings(
-        fs.path.join(appDirectory, 'ios', 'Runner.xcodeproj'),
+        iosProject.directory.childFile('Runner.xcodeproj').path,
         'Runner',
       ).containsKey('SWIFT_VERSION');
       final File podfileTemplate = fs.file(fs.path.join(
@@ -173,15 +173,14 @@
         'cocoapods',
         isSwift ? 'Podfile-swift' : 'Podfile-objc',
       ));
-      podfileTemplate.copySync(podfilePath);
+      podfileTemplate.copySync(podfile.path);
     }
-
-    _addPodsDependencyToFlutterXcconfig(appDirectory, 'Debug');
-    _addPodsDependencyToFlutterXcconfig(appDirectory, 'Release');
+    _addPodsDependencyToFlutterXcconfig(iosProject, 'Debug');
+    _addPodsDependencyToFlutterXcconfig(iosProject, 'Release');
   }
 
-  void _addPodsDependencyToFlutterXcconfig(String appDirectory, String mode) {
-    final File file = fs.file(fs.path.join(appDirectory, 'ios', 'Flutter', '$mode.xcconfig'));
+  void _addPodsDependencyToFlutterXcconfig(IosProject iosProject, String mode) {
+    final File file = iosProject.xcodeConfigFor(mode);
     if (file.existsSync()) {
       final String content = file.readAsStringSync();
       final String include = '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.${mode
@@ -192,12 +191,11 @@
   }
 
   /// Ensures that pod install is deemed needed on next check.
-  void invalidatePodInstallOutput(String appDirectory) {
-    final File manifest = fs.file(
-      fs.path.join(appDirectory, 'ios', 'Pods', 'Manifest.lock'),
-    );
-    if (manifest.existsSync())
-      manifest.deleteSync();
+  void invalidatePodInstallOutput(IosProject iosProject) {
+    final File manifestLock = iosProject.podManifestLock;
+    if (manifestLock.existsSync()) {
+      manifestLock.deleteSync();
+    }
   }
 
   // Check if you need to run pod install.
@@ -206,24 +204,25 @@
   // 2. Podfile.lock doesn't exist or is older than Podfile
   // 3. Pods/Manifest.lock doesn't exist (It is deleted when plugins change)
   // 4. Podfile.lock doesn't match Pods/Manifest.lock.
-  bool _shouldRunPodInstall(Directory appIosDirectory, bool dependenciesChanged) {
+  bool _shouldRunPodInstall(IosProject iosProject, bool dependenciesChanged) {
     if (dependenciesChanged)
       return true;
-    final File podfileFile = appIosDirectory.childFile('Podfile');
-    final File podfileLockFile = appIosDirectory.childFile('Podfile.lock');
-    final File manifestLockFile =
-        appIosDirectory.childFile(fs.path.join('Pods', 'Manifest.lock'));
+
+    final File podfileFile = iosProject.podfile;
+    final File podfileLockFile = iosProject.podfileLock;
+    final File manifestLockFile = iosProject.podManifestLock;
+
     return !podfileLockFile.existsSync()
         || !manifestLockFile.existsSync()
         || podfileLockFile.statSync().modified.isBefore(podfileFile.statSync().modified)
         || podfileLockFile.readAsStringSync() != manifestLockFile.readAsStringSync();
   }
 
-  Future<Null> _runPodInstall(Directory appIosDirectory, String engineDirectory) async {
+  Future<Null> _runPodInstall(IosProject iosProject, String engineDirectory) async {
     final Status status = logger.startProgress('Running pod install...', expectSlowOperation: true);
     final ProcessResult result = await processManager.run(
       <String>['pod', 'install', '--verbose'],
-      workingDirectory: appIosDirectory.path,
+      workingDirectory: iosProject.directory.path,
       environment: <String, String>{
         // For backward compatibility with previously created Podfile only.
         'FLUTTER_FRAMEWORK_DIR': engineDirectory,
@@ -244,7 +243,7 @@
       }
     }
     if (result.exitCode != 0) {
-      invalidatePodInstallOutput(appIosDirectory.parent.path);
+      invalidatePodInstallOutput(iosProject);
       _diagnosePodInstallFailure(result);
       throwToolExit('Error running pod install');
     }
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 85d4d87..63c8da9 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -20,9 +20,9 @@
 import '../base/process_manager.dart';
 import '../base/utils.dart';
 import '../build_info.dart';
-import '../flutter_manifest.dart';
 import '../globals.dart';
 import '../plugins.dart';
+import '../project.dart';
 import '../services.dart';
 import 'cocoapods.dart';
 import 'code_signing.dart';
@@ -233,18 +233,15 @@
   final Directory appDirectory = fs.directory(app.appDirectory);
   await _addServicesToBundle(appDirectory);
 
-  final FlutterManifest manifest = await FlutterManifest.createFromPath(
-    fs.currentDirectory.childFile('pubspec.yaml').path,
-  );
-  updateGeneratedXcodeProperties(
-    projectPath: fs.currentDirectory.path,
-    buildInfo: buildInfo,
+  final FlutterProject project = new FlutterProject(fs.currentDirectory);
+  await updateGeneratedXcodeProperties(
+    project: project,
     targetOverride: targetOverride,
     previewDart2: buildInfo.previewDart2,
-    manifest: manifest,
+    buildInfo: buildInfo,
   );
 
-  if (hasPlugins()) {
+  if (hasPlugins(project)) {
     final String iosPath = fs.path.join(fs.currentDirectory.path, app.appDirectory);
     // If the Xcode project, Podfile, or Generated.xcconfig have changed since
     // last run, pods should be updated.
@@ -258,7 +255,7 @@
       properties: <String, String>{},
     );
     final bool didPodInstall = await cocoaPods.processPods(
-      appIosDirectory: appDirectory,
+      iosProject: project.ios,
       iosEngineDir: flutterFrameworkDir(buildInfo.mode),
       isSwift: app.isSwift,
       dependenciesChanged: !await fingerprinter.doesFingerprintMatch()
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index b9bf73d..4f7f6b5 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
+
 import 'package:meta/meta.dart';
 
 import '../artifacts.dart';
@@ -17,6 +19,7 @@
 import '../cache.dart';
 import '../flutter_manifest.dart';
 import '../globals.dart';
+import '../project.dart';
 
 final RegExp _settingExpr = new RegExp(r'(\w+)\s*=\s*(.*)$');
 final RegExp _varExpr = new RegExp(r'\$\((.*)\)');
@@ -25,27 +28,18 @@
   return fs.path.normalize(fs.path.dirname(artifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, mode)));
 }
 
-String _generatedXcodePropertiesPath({@required String projectPath, @required FlutterManifest manifest}) {
-  if (manifest.isModule) {
-    return fs.path.join(projectPath, '.ios', 'Flutter', 'Generated.xcconfig');
-  } else {
-    return fs.path.join(projectPath, 'ios', 'Flutter', 'Generated.xcconfig');
-  }
-}
-
 /// Writes default Xcode properties files in the Flutter project at [projectPath],
 /// if project is an iOS project and such files are out of date or do not
 /// already exist.
-void generateXcodeProperties({String projectPath, FlutterManifest manifest}) {
-  if (manifest.isModule || fs.isDirectorySync(fs.path.join(projectPath, 'ios'))) {
-    final File propertiesFile = fs.file(_generatedXcodePropertiesPath(projectPath: projectPath, manifest: manifest));
-    if (!Cache.instance.fileOlderThanToolsStamp(propertiesFile)) {
+Future<void> generateXcodeProperties({FlutterProject project}) async {
+  if ((await project.manifest).isModule ||
+      project.ios.directory.existsSync()) {
+    if (!Cache.instance.fileOlderThanToolsStamp(await project.generatedXcodePropertiesFile)) {
       return;
     }
 
-    updateGeneratedXcodeProperties(
-      projectPath: projectPath,
-      manifest: manifest,
+    await updateGeneratedXcodeProperties(
+      project: project,
       buildInfo: BuildInfo.debug,
       targetOverride: bundle.defaultMainPath,
       previewDart2: true,
@@ -57,13 +51,12 @@
 ///
 /// targetOverride: Optional parameter, if null or unspecified the default value
 /// from xcode_backend.sh is used 'lib/main.dart'.
-void updateGeneratedXcodeProperties({
-  @required String projectPath,
-  @required FlutterManifest manifest,
+Future<void> updateGeneratedXcodeProperties({
+  @required FlutterProject project,
   @required BuildInfo buildInfo,
   String targetOverride,
   @required bool previewDart2,
-}) {
+}) async {
   final StringBuffer localsBuffer = new StringBuffer();
 
   localsBuffer.writeln('// This is a generated file; do not edit or check into version control.');
@@ -72,7 +65,7 @@
   localsBuffer.writeln('FLUTTER_ROOT=$flutterRoot');
 
   // This holds because requiresProjectRoot is true for this command
-  localsBuffer.writeln('FLUTTER_APPLICATION_PATH=${fs.path.normalize(projectPath)}');
+  localsBuffer.writeln('FLUTTER_APPLICATION_PATH=${fs.path.normalize(project.directory.path)}');
 
   // Relative to FLUTTER_APPLICATION_PATH, which is [Directory.current].
   if (targetOverride != null)
@@ -86,6 +79,7 @@
 
   localsBuffer.writeln('SYMROOT=\${SOURCE_ROOT}/../${getIosBuildDirectory()}');
 
+  final FlutterManifest manifest = await project.manifest;
   if (!manifest.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
@@ -125,9 +119,9 @@
     localsBuffer.writeln('TRACK_WIDGET_CREATION=true');
   }
 
-  final File localsFile = fs.file(_generatedXcodePropertiesPath(projectPath: projectPath, manifest: manifest));
-  localsFile.createSync(recursive: true);
-  localsFile.writeAsStringSync(localsBuffer.toString());
+  final File generatedXcodePropertiesFile = await project.generatedXcodePropertiesFile;
+  generatedXcodePropertiesFile.createSync(recursive: true);
+  generatedXcodePropertiesFile.writeAsStringSync(localsBuffer.toString());
 }
 
 XcodeProjectInterpreter get xcodeProjectInterpreter => context[XcodeProjectInterpreter];
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index d9e2946..484d897 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -2,15 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:meta/meta.dart';
+import 'dart:async';
+
 import 'package:mustache/mustache.dart' as mustache;
 import 'package:yaml/yaml.dart';
 
 import 'base/file_system.dart';
 import 'dart/package_map.dart';
-import 'flutter_manifest.dart';
 import 'globals.dart';
 import 'ios/cocoapods.dart';
+import 'project.dart';
 
 void _renderTemplateToFile(String template, dynamic context, String filePath) {
   final String renderedTemplate =
@@ -69,11 +70,11 @@
   return new Plugin.fromYaml(name, packageRootPath, flutterConfig['plugin']);
 }
 
-List<Plugin> findPlugins(String directory) {
+List<Plugin> findPlugins(FlutterProject project) {
   final List<Plugin> plugins = <Plugin>[];
   Map<String, Uri> packages;
   try {
-    final String packagesFile = fs.path.join(directory, PackageMap.globalPackagesPath);
+    final String packagesFile = fs.path.join(project.directory.path, PackageMap.globalPackagesPath);
     packages = new PackageMap(packagesFile).map;
   } on FormatException catch (e) {
     printTrace('Invalid .packages file: $e');
@@ -89,9 +90,9 @@
 }
 
 /// Returns true if .flutter-plugins has changed, otherwise returns false.
-bool _writeFlutterPluginsList(String directory, List<Plugin> plugins) {
-  final File pluginsFile = fs.file(fs.path.join(directory, '.flutter-plugins'));
-  final String oldContents = _readFlutterPluginsList(directory);
+bool _writeFlutterPluginsList(FlutterProject project, List<Plugin> plugins) {
+  final File pluginsFile = project.flutterPluginsFile;
+  final String oldContents = _readFlutterPluginsList(project);
   final String pluginManifest =
       plugins.map((Plugin p) => '${p.name}=${escapePath(p.path)}').join('\n');
   if (pluginManifest.isNotEmpty) {
@@ -100,15 +101,16 @@
     if (pluginsFile.existsSync())
       pluginsFile.deleteSync();
   }
-  final String newContents = _readFlutterPluginsList(directory);
+  final String newContents = _readFlutterPluginsList(project);
   return oldContents != newContents;
 }
 
 /// Returns the contents of the `.flutter-plugins` file in [directory], or
 /// null if that file does not exist.
-String _readFlutterPluginsList(String directory) {
-  final File pluginsFile = fs.file(fs.path.join(directory, '.flutter-plugins'));
-  return pluginsFile.existsSync() ? pluginsFile.readAsStringSync() : null;
+String _readFlutterPluginsList(FlutterProject project) {
+  return project.flutterPluginsFile.existsSync()
+      ? project.flutterPluginsFile.readAsStringSync()
+      : null;
 }
 
 const String _androidPluginRegistryTemplate = '''package io.flutter.plugins;
@@ -142,7 +144,7 @@
 }
 ''';
 
-void _writeAndroidPluginRegistrant(String directory, List<Plugin> plugins) {
+Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
   final List<Map<String, dynamic>> androidPlugins = plugins
       .where((Plugin p) => p.androidPackage != null && p.pluginClass != null)
       .map((Plugin p) => <String, dynamic>{
@@ -155,8 +157,19 @@
     'plugins': androidPlugins,
   };
 
-  final String javaSourcePath = fs.path.join(directory, 'src', 'main', 'java');
-  final String registryPath = fs.path.join(javaSourcePath, 'io', 'flutter', 'plugins', 'GeneratedPluginRegistrant.java');
+  final String javaSourcePath = fs.path.join(
+    (await project.androidPluginRegistrantHost).path,
+    'src',
+    'main',
+    'java',
+  );
+  final String registryPath = fs.path.join(
+    javaSourcePath,
+    'io',
+    'flutter',
+    'plugins',
+    'GeneratedPluginRegistrant.java',
+  );
   _renderTemplateToFile(_androidPluginRegistryTemplate, context, registryPath);
 }
 
@@ -221,7 +234,7 @@
 end
 ''';
 
-void _writeIOSPluginRegistrant(String directory, FlutterManifest manifest, List<Plugin> plugins) {
+Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
   final List<Map<String, dynamic>> iosPlugins = plugins
       .where((Plugin p) => p.pluginClass != null)
       .map((Plugin p) => <String, dynamic>{
@@ -234,10 +247,8 @@
     'plugins': iosPlugins,
   };
 
-  if (manifest.isModule) {
-    // In a module create the GeneratedPluginRegistrant as a pod to be included
-    // from a hosting app.
-    final String registryDirectory = fs.path.join(directory, 'Flutter', 'FlutterPluginRegistrant');
+  final String registryDirectory = (await project.iosPluginRegistrantHost).path;
+  if ((await project.manifest).isModule) {
     final String registryClassesDirectory = fs.path.join(registryDirectory, 'Classes');
     _renderTemplateToFile(
       _iosPluginRegistrantPodspecTemplate,
@@ -255,57 +266,37 @@
       fs.path.join(registryClassesDirectory, 'GeneratedPluginRegistrant.m'),
     );
   } else {
-    // For a non-module create the GeneratedPluginRegistrant as source files
-    // directly in the ios project.
-    final String runnerDirectory = fs.path.join(directory, 'Runner');
     _renderTemplateToFile(
       _iosPluginRegistryHeaderTemplate,
       context,
-      fs.path.join(runnerDirectory, 'GeneratedPluginRegistrant.h'),
+      fs.path.join(registryDirectory, 'GeneratedPluginRegistrant.h'),
     );
     _renderTemplateToFile(
       _iosPluginRegistryImplementationTemplate,
       context,
-      fs.path.join(runnerDirectory, 'GeneratedPluginRegistrant.m'),
+      fs.path.join(registryDirectory, 'GeneratedPluginRegistrant.m'),
     );
   }
 }
 
-class InjectPluginsResult{
-  InjectPluginsResult({
-    @required this.hasPlugin,
-    @required this.hasChanged,
-  });
-  /// True if any flutter plugin exists, otherwise false.
-  final bool hasPlugin;
-  /// True if plugins have changed since last build.
-  final bool hasChanged;
-}
-
 /// Injects plugins found in `pubspec.yaml` into the platform-specific projects.
-void injectPlugins({@required String projectPath, @required FlutterManifest manifest}) {
-  final List<Plugin> plugins = findPlugins(projectPath);
-  final bool changed = _writeFlutterPluginsList(projectPath, plugins);
-  if (manifest.isModule) {
-    _writeAndroidPluginRegistrant(fs.path.join(projectPath, '.android', 'Flutter'), plugins);
-  } else if (fs.isDirectorySync(fs.path.join(projectPath, 'android', 'app'))) {
-    _writeAndroidPluginRegistrant(fs.path.join(projectPath, 'android', 'app'), plugins);
-  }
-  if (manifest.isModule) {
-    _writeIOSPluginRegistrant(fs.path.join(projectPath, '.ios'), manifest, plugins);
-  } else if (fs.isDirectorySync(fs.path.join(projectPath, 'ios'))) {
-    _writeIOSPluginRegistrant(fs.path.join(projectPath, 'ios'), manifest, plugins);
+Future<void> injectPlugins(FlutterProject project) async {
+  final List<Plugin> plugins = findPlugins(project);
+  final bool changed = _writeFlutterPluginsList(project, plugins);
+  await _writeAndroidPluginRegistrant(project, plugins);
+  await _writeIOSPluginRegistrant(project, plugins);
+
+  if (project.ios.directory.existsSync()) {
     final CocoaPods cocoaPods = new CocoaPods();
     if (plugins.isNotEmpty)
-      cocoaPods.setupPodfile(projectPath, manifest);
+      cocoaPods.setupPodfile(project.ios);
     if (changed)
-      cocoaPods.invalidatePodInstallOutput(projectPath);
+      cocoaPods.invalidatePodInstallOutput(project.ios);
   }
 }
 
 /// Returns whether the Flutter project at the specified [directory]
 /// has any plugin dependencies.
-bool hasPlugins({String directory}) {
-  directory ??= fs.currentDirectory.path;
-  return _readFlutterPluginsList(directory) != null;
+bool hasPlugins(FlutterProject project) {
+  return _readFlutterPluginsList(project) != null;
 }
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index d3f2e66..ad506d2 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -5,7 +5,6 @@
 import 'dart:async';
 import 'dart:convert';
 
-
 import 'android/gradle.dart' as gradle;
 import 'base/file_system.dart';
 import 'bundle.dart' as bundle;
@@ -67,6 +66,57 @@
   /// The generated IosModule sub project of this module project.
   IosModuleProject get iosModule => new IosModuleProject(directory.childDirectory('.ios'));
 
+  Future<File> get androidLocalPropertiesFile {
+    return _androidLocalPropertiesFile ??= manifest.then<File>((FlutterManifest manifest) {
+      return directory.childDirectory(manifest.isModule ? '.android' : 'android')
+          .childFile('local.properties');
+    });
+  }
+  Future<File> _androidLocalPropertiesFile;
+
+  Future<File> get generatedXcodePropertiesFile {
+    return _generatedXcodeProperties ??= manifest.then<File>((FlutterManifest manifest) {
+      return directory.childDirectory(manifest.isModule ? '.ios' : 'ios')
+          .childDirectory('Flutter')
+          .childFile('Generated.xcconfig');
+    });
+  }
+  Future<File> _generatedXcodeProperties;
+
+  File get flutterPluginsFile {
+    return _flutterPluginsFile ??= directory.childFile('.flutter-plugins');
+  }
+  File _flutterPluginsFile;
+
+  Future<Directory> get androidPluginRegistrantHost async {
+    return _androidPluginRegistrantHost ??= manifest.then((FlutterManifest manifest) {
+      if (manifest.isModule) {
+        return directory.childDirectory('.android').childDirectory('Flutter');
+      } else {
+        return directory.childDirectory('android').childDirectory('app');
+      }
+    });
+  }
+  Future<Directory> _androidPluginRegistrantHost;
+
+  Future<Directory> get iosPluginRegistrantHost async {
+    return _iosPluginRegistrantHost ??= manifest.then((FlutterManifest manifest) {
+      if (manifest.isModule) {
+        // In a module create the GeneratedPluginRegistrant as a pod to be included
+        // from a hosting app.
+        return directory
+            .childDirectory('.ios')
+            .childDirectory('Flutter')
+            .childDirectory('FlutterPluginRegistrant');
+      } else {
+        // For a non-module create the GeneratedPluginRegistrant as source files
+        // directly in the iOS project.
+        return directory.childDirectory('ios').childDirectory('Runner');
+      }
+    });
+  }
+  Future<Directory> _iosPluginRegistrantHost;
+
   /// Returns true if this project has an example application
   bool get hasExampleApp => _exampleDirectory.childFile('pubspec.yaml').existsSync();
 
@@ -86,11 +136,11 @@
     }
     final FlutterManifest manifest = await this.manifest;
     if (manifest.isModule) {
-      await androidModule.ensureReadyForPlatformSpecificTooling(manifest);
-      await iosModule.ensureReadyForPlatformSpecificTooling(manifest);
+      await androidModule.ensureReadyForPlatformSpecificTooling(this);
+      await iosModule.ensureReadyForPlatformSpecificTooling();
     }
-    xcode.generateXcodeProperties(projectPath: directory.path, manifest: manifest);
-    injectPlugins(projectPath: directory.path, manifest: manifest);
+    await xcode.generateXcodeProperties(project: this);
+    await injectPlugins(this);
   }
 }
 
@@ -101,6 +151,20 @@
 
   final Directory directory;
 
+  /// The xcode config file for [mode].
+  File xcodeConfigFor(String mode) {
+    return directory.childDirectory('Flutter').childFile('$mode.xcconfig');
+  }
+
+  /// The 'Podfile'.
+  File get podfile => directory.childFile('Podfile');
+
+  /// The 'Podfile.lock'.
+  File get podfileLock => directory.childFile('Podfile.lock');
+
+  /// The 'Manifest.lock'.
+  File get podManifestLock => directory.childDirectory('Pods').childFile('Manifest.lock');
+
   Future<String> productBundleIdentifier() {
     final File projectFile = directory.childDirectory('Runner.xcodeproj').childFile('project.pbxproj');
     return _firstMatchInFile(projectFile, _productBundleIdPattern).then((Match match) => match?.group(1));
@@ -114,7 +178,7 @@
 
   final Directory directory;
 
-  Future<void> ensureReadyForPlatformSpecificTooling(FlutterManifest manifest) async {
+  Future<void> ensureReadyForPlatformSpecificTooling() async {
     if (_shouldRegenerate()) {
       final Template template = new Template.fromName(fs.path.join('module', 'ios'));
       template.render(directory, <String, dynamic>{}, printStatusWhenWriting: false);
@@ -133,6 +197,29 @@
 
   AndroidProject(this.directory);
 
+  File get gradleManifestFile {
+    return _gradleManifestFile ??= isUsingGradle()
+        ? fs.file(fs.path.join(directory.path, 'app', 'src', 'main', 'AndroidManifest.xml'))
+        : directory.childFile('AndroidManifest.xml');
+  }
+  File _gradleManifestFile;
+
+
+  File get gradleAppOutV1File {
+    return _gradleAppOutV1File ??= gradleAppOutV1Directory.childFile('app-debug.apk');
+  }
+  File _gradleAppOutV1File;
+
+  Directory get gradleAppOutV1Directory {
+    return _gradleAppOutV1Directory ??= fs.directory(fs.path.join(directory.path, 'app', 'build', 'outputs', 'apk'));
+  }
+  Directory _gradleAppOutV1Directory;
+
+
+  bool isUsingGradle() {
+    return directory.childFile('build.gradle').existsSync();
+  }
+  
   final Directory directory;
 
   Future<String> applicationId() {
@@ -153,15 +240,15 @@
 
   final Directory directory;
 
-  Future<void> ensureReadyForPlatformSpecificTooling(FlutterManifest manifest) async {
+  Future<void> ensureReadyForPlatformSpecificTooling(FlutterProject project) async {
     if (_shouldRegenerate()) {
       final Template template = new Template.fromName(fs.path.join('module', 'android'));
       template.render(directory, <String, dynamic>{
-        'androidIdentifier': manifest.moduleDescriptor['androidPackage'],
+        'androidIdentifier': (await project.manifest).moduleDescriptor['androidPackage'],
       }, printStatusWhenWriting: false);
       gradle.injectGradleWrapper(directory);
     }
-    gradle.updateLocalPropertiesSync(directory, manifest);
+    await gradle.updateLocalProperties(project: project, requireAndroidSdk: false);
   }
 
   bool _shouldRegenerate() {
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 0d6f6cb..ca16780 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -6,7 +6,6 @@
 
 import 'package:meta/meta.dart';
 
-import 'android/gradle.dart';
 import 'application_package.dart';
 import 'artifacts.dart';
 import 'asset.dart';
@@ -24,6 +23,7 @@
 import 'devfs.dart';
 import 'device.dart';
 import 'globals.dart';
+import 'project.dart';
 import 'run_cold.dart';
 import 'run_hot.dart';
 import 'vmservice.dart';
@@ -814,15 +814,11 @@
         new DartDependencySetBuilder(mainPath, packagesFilePath);
     final DependencyChecker dependencyChecker =
         new DependencyChecker(dartDependencySetBuilder, assetBundle);
-    final String path = device.package.packagePath;
-    if (path == null)
+    if (device.package.packagesFile == null || !device.package.packagesFile.existsSync()) {
       return true;
-    final FileStat stat = fs.file(path).statSync();
-    if (stat.type != FileSystemEntityType.FILE) // ignore: deprecated_member_use
-      return true;
-    if (!fs.file(path).existsSync())
-      return true;
-    final DateTime lastBuildTime = stat.modified;
+    }
+    final DateTime lastBuildTime = device.package.packagesFile.statSync().modified;
+
     return dependencyChecker.check(lastBuildTime);
   }
 
@@ -906,11 +902,9 @@
     case TargetPlatform.android_arm64:
     case TargetPlatform.android_x64:
     case TargetPlatform.android_x86:
-      String manifest = 'android/AndroidManifest.xml';
-      if (isProjectUsingGradle()) {
-        manifest = gradleManifestPath;
-      }
-      return 'Is your project missing an $manifest?\nConsider running "flutter create ." to create one.';
+      final FlutterProject project = new FlutterProject(fs.currentDirectory);
+      final String manifestPath = fs.path.relative(project.android.gradleManifestFile.path);
+      return 'Is your project missing an $manifestPath?\nConsider running "flutter create ." to create one.';
     case TargetPlatform.ios:
       return 'Is your project missing an ios/Runner/Info.plist?\nConsider running "flutter create ." to create one.';
     default:
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart
index 6273196..a7fddb2 100644
--- a/packages/flutter_tools/lib/src/run_cold.dart
+++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -30,7 +30,7 @@
              ipv6: ipv6);
 
   final bool traceStartup;
-  final String applicationBinary;
+  final File applicationBinary;
 
   @override
   Future<int> run({
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 886140a..f28cf49 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -64,7 +64,7 @@
              ipv6: ipv6);
 
   final bool benchmarkMode;
-  final String applicationBinary;
+  final File applicationBinary;
   final bool hostIsIde;
   Set<String> _dartDependencies;
   final String dillOutputPath;
diff --git a/packages/flutter_tools/lib/src/tester/flutter_tester.dart b/packages/flutter_tools/lib/src/tester/flutter_tester.dart
index 6d247d1..9fb1973 100644
--- a/packages/flutter_tools/lib/src/tester/flutter_tester.dart
+++ b/packages/flutter_tools/lib/src/tester/flutter_tester.dart
@@ -22,21 +22,21 @@
 import '../version.dart';
 
 class FlutterTesterApp extends ApplicationPackage {
-  final String _directory;
+  final Directory _directory;
 
   factory FlutterTesterApp.fromCurrentDirectory() {
-    return new FlutterTesterApp._(fs.currentDirectory.path);
+    return new FlutterTesterApp._(fs.currentDirectory);
   }
 
-  FlutterTesterApp._(String directory)
+  FlutterTesterApp._(Directory directory)
       : _directory = directory,
-        super(id: directory);
+        super(id: directory.path);
 
   @override
-  String get name => fs.path.basename(_directory);
+  String get name => _directory.basename;
 
   @override
-  String get packagePath => fs.path.join(_directory, '.packages');
+  File get packagesFile => _directory.childFile('.packages');
 }
 
 // TODO(scheglov): This device does not currently work with full restarts.