Add loader screen for --hot mode (#5113)

diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart
index b8528c4..822f69d 100644
--- a/packages/flutter_tools/lib/src/asset.dart
+++ b/packages/flutter_tools/lib/src/asset.dart
@@ -37,6 +37,7 @@
   }
 
   bool get isStringEntry => _contents != null;
+  int get contentsLength => _contents.length;
 
   final File file;
   final String _contents;
diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart
index de56314..c46fac9 100644
--- a/packages/flutter_tools/lib/src/devfs.dart
+++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -49,6 +49,17 @@
     return _fileStat.modified.isAfter(_oldFileStat.modified);
   }
 
+  int get size {
+    if (_isSourceEntry) {
+      return bundleEntry.contentsLength;
+    } else {
+      if (_fileStat == null) {
+        _stat();
+      }
+      return _fileStat.size;
+    }
+  }
+
   void _stat() {
     if (_isSourceEntry)
       return;
@@ -151,6 +162,8 @@
   final Directory rootDirectory;
   final Map<String, DevFSEntry> _entries = <String, DevFSEntry>{};
   final List<Future<Response>> _pendingWrites = new List<Future<Response>>();
+  int _bytes = 0;
+  int get bytes => _bytes;
   Uri _baseUri;
   Uri get baseUri => _baseUri;
 
@@ -166,6 +179,7 @@
   }
 
   Future<dynamic> update([AssetBundle bundle = null]) async {
+    _bytes = 0;
     // Mark all entries as not seen.
     _entries.forEach((String path, DevFSEntry entry) {
       entry._wasSeen = false;
@@ -244,6 +258,7 @@
     entry._wasSeen = true;
     bool needsWrite = entry.isModified;
     if (needsWrite) {
+      _bytes += entry.size;
       Future<dynamic> pendingWrite = _operations.writeFile(fsName, entry);
       if (pendingWrite != null) {
         _pendingWrites.add(pendingWrite);
@@ -261,11 +276,15 @@
       _entries[devicePath] = entry;
     }
     entry._wasSeen = true;
-    Future<dynamic> pendingWrite = _operations.writeFile(fsName, entry);
-    if (pendingWrite != null) {
-      _pendingWrites.add(pendingWrite);
-    } else {
-      printTrace('DevFS: Failed to sync "$devicePath"');
+    bool needsWrite = entry.isModified;
+    if (needsWrite) {
+      _bytes += entry.size;
+      Future<dynamic> pendingWrite = _operations.writeFile(fsName, entry);
+      if (pendingWrite != null) {
+        _pendingWrites.add(pendingWrite);
+      } else {
+        printTrace('DevFS: Failed to sync "$devicePath"');
+      }
     }
   }
 
diff --git a/packages/flutter_tools/lib/src/run.dart b/packages/flutter_tools/lib/src/run.dart
index d261c70..7eac9b7 100644
--- a/packages/flutter_tools/lib/src/run.dart
+++ b/packages/flutter_tools/lib/src/run.dart
@@ -11,6 +11,7 @@
 import 'base/logger.dart';
 import 'base/utils.dart';
 import 'build_info.dart';
+import 'cache.dart';
 import 'commands/build_apk.dart';
 import 'commands/install.dart';
 import 'commands/trace.dart';
@@ -31,6 +32,15 @@
     return targetPath;
 }
 
+String getDevFSLoaderScript() {
+  return path.absolute(path.join(Cache.flutterRoot,
+                                 'packages',
+                                 'flutter',
+                                 'bin',
+                                 'loader',
+                                 'loader_app.dart'));
+}
+
 class RunAndStayResident {
   RunAndStayResident(
     this.device, {
@@ -176,7 +186,9 @@
     if (traceStartup != null)
       platformArgs = <String, dynamic>{ 'trace-startup': traceStartup };
 
-    printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...');
+    if (!hotMode || (hotMode && !device.needsDevFS))
+      printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...');
+
 
     _loggingSubscription = device.logReader.logLines.listen((String line) {
       if (!line.contains('Observatory listening on http') && !line.contains('Diagnostic server listening on http'))
@@ -186,7 +198,7 @@
     _result = await device.startApp(
       _package,
       debuggingOptions.buildMode,
-      mainPath: _mainPath,
+      mainPath: (hotMode && device.needsDevFS) ? getDevFSLoaderScript() : _mainPath,
       debuggingOptions: debuggingOptions,
       platformArgs: platformArgs,
       route: route
@@ -213,7 +225,7 @@
           printError('Could not perform initial file synchronization.');
           return 3;
         }
-        printStatus('Launching from sources.');
+        printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...');
         await _launchFromDevFS(_package, _mainPath);
       }
       observatory.populateIsolateInfo();
@@ -345,9 +357,10 @@
       });
     }
 
-    Status devFSStatus = logger.startProgress('Updating files on device...');
+    Status devFSStatus = logger.startProgress('Syncing files on device...');
     await _devFS.update();
     devFSStatus.stop(showElapsedTime: true);
+    printStatus('Synced ${getSizeAsMB(_devFS.bytes)} MB');
     return true;
   }
 
@@ -388,7 +401,13 @@
     reloadStatus.stop(showElapsedTime: true);
     Status reassembleStatus =
         logger.startProgress('Reassembling application');
-    await observatory.flutterReassemble(observatory.firstIsolateId);
+    try {
+      await observatory.flutterReassemble(observatory.firstIsolateId);
+    } catch (_) {
+      reassembleStatus.stop(showElapsedTime: true);
+      printError('Reassembling application failed.');
+      return false;
+    }
     reassembleStatus.stop(showElapsedTime: true);
     return true;
   }