diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle index 3b4e0c5f02c..1ca71d7fc94 100644 --- a/packages/flutter_tools/gradle/flutter.gradle +++ b/packages/flutter_tools/gradle/flutter.gradle @@ -893,11 +893,7 @@ abstract class BaseFlutterTask extends DefaultTask { // cache. String[] ruleNames; if (buildMode == "debug") { - if (fastStart) { - ruleNames = ["faststart_android_application"] - } else { - ruleNames = ["debug_android_application"] - } + ruleNames = ["debug_android_application"] } else { ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" } } diff --git a/packages/flutter_tools/lib/src/build_system/targets/android.dart b/packages/flutter_tools/lib/src/build_system/targets/android.dart index 82713a6885a..98469f5475a 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/android.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart @@ -34,11 +34,9 @@ abstract class AndroidAssetBundle extends Target { @override List get depfiles => [ - if (_copyAssets) - 'flutter_assets.d', + 'flutter_assets.d', ]; - bool get _copyAssets => true; @override Future build(Environment environment) async { @@ -61,21 +59,19 @@ abstract class AndroidAssetBundle extends Target { environment.fileSystem.file(isolateSnapshotData) .copySync(outputDirectory.childFile('isolate_snapshot_data').path); } - if (_copyAssets) { - final Depfile assetDepfile = await copyAssets( - environment, - outputDirectory, - targetPlatform: TargetPlatform.android, - ); - final DepfileService depfileService = DepfileService( - fileSystem: environment.fileSystem, - logger: environment.logger, - ); - depfileService.writeToFile( - assetDepfile, - environment.buildDir.childFile('flutter_assets.d'), - ); - } + final Depfile assetDepfile = await copyAssets( + environment, + outputDirectory, + targetPlatform: TargetPlatform.android, + ); + final DepfileService depfileService = DepfileService( + fileSystem: environment.fileSystem, + logger: environment.logger, + ); + depfileService.writeToFile( + assetDepfile, + environment.buildDir.childFile('flutter_assets.d'), + ); } @override @@ -108,17 +104,6 @@ class DebugAndroidApplication extends AndroidAssetBundle { ]; } -/// A minimal android application that does not include assets. -class FastStartAndroidApplication extends DebugAndroidApplication { - const FastStartAndroidApplication(); - - @override - String get name => 'faststart_android_application'; - - @override - bool get _copyAssets => false; -} - /// An implementation of [AndroidAssetBundle] that only includes assets. class AotAndroidAssetBundle extends AndroidAssetBundle { const AotAndroidAssetBundle(); diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart index d536feb7dfa..fd400e5eb57 100644 --- a/packages/flutter_tools/lib/src/commands/assemble.dart +++ b/packages/flutter_tools/lib/src/commands/assemble.dart @@ -49,7 +49,6 @@ const List _kDefaultTargets = [ CopyFlutterBundle(), // Android targets, DebugAndroidApplication(), - FastStartAndroidApplication(), ProfileAndroidApplication(), // Android ABI specific AOT rules. androidArmProfileBundle, diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart index 6f40efa3131..f684b81bf4e 100644 --- a/packages/flutter_tools/lib/src/devfs.dart +++ b/packages/flutter_tools/lib/src/devfs.dart @@ -472,7 +472,6 @@ class DevFS { String dillOutputPath, bool fullRestart = false, String projectRootPath, - bool skipAssets = false, }) async { assert(trackWidgetCreation != null); assert(generator != null); @@ -484,24 +483,23 @@ class DevFS { final Map dirtyEntries = {}; int syncedBytes = 0; - if (bundle != null && !skipAssets) { + if (bundle != null) { final String assetBuildDirPrefix = _asUriPath(getAssetBuildDirectory()); - // We write the assets into the AssetBundle working dir so that they + // The tool writes the assets into the AssetBundle working dir so that they // are in the same location in DevFS and the iOS simulator. final String assetDirectory = getAssetBuildDirectory(); bundle.entries.forEach((String archivePath, DevFSContent content) { + if (!content.isModified || bundleFirstUpload) { + return; + } final Uri deviceUri = _fileSystem.path.toUri(_fileSystem.path.join(assetDirectory, archivePath)); if (deviceUri.path.startsWith(assetBuildDirPrefix)) { archivePath = deviceUri.path.substring(assetBuildDirPrefix.length); } - // Only update assets if they have been modified, or if this is the - // first upload of the asset bundle. - if (content.isModified || (bundleFirstUpload && archivePath != null)) { - dirtyEntries[deviceUri] = content; - syncedBytes += content.size; - if (archivePath != null && !bundleFirstUpload) { - assetPathsToEvict.add(archivePath); - } + dirtyEntries[deviceUri] = content; + syncedBytes += content.size; + if (archivePath != null && !bundleFirstUpload) { + assetPathsToEvict.add(archivePath); } }); } diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart index e6ce4637126..feb31ba1a9c 100644 --- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart +++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart @@ -818,7 +818,6 @@ class WebDevFS implements DevFS { String dillOutputPath, bool fullRestart = false, String projectRootPath, - bool skipAssets = false, }) async { assert(trackWidgetCreation != null); assert(generator != null); diff --git a/packages/flutter_tools/test/integration.shard/hot_reload_with_asset_test.dart b/packages/flutter_tools/test/integration.shard/hot_reload_with_asset_test.dart new file mode 100644 index 00000000000..eb4737036fb --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/hot_reload_with_asset_test.dart @@ -0,0 +1,83 @@ +// Copyright 2014 The Flutter 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 'dart:async'; + +import 'package:file/file.dart'; + +import '../src/common.dart'; +import 'test_data/hot_reload_with_asset.dart'; +import 'test_driver.dart'; +import 'test_utils.dart'; + +void main() { + Directory tempDir; + final HotReloadWithAssetProject project = HotReloadWithAssetProject(); + FlutterRunTestDriver flutter; + + setUp(() async { + tempDir = createResolvedTempDirectorySync('hot_reload_test.'); + await project.setUpIn(tempDir); + flutter = FlutterRunTestDriver(tempDir); + }); + + tearDown(() async { + await flutter?.stop(); + tryToDelete(tempDir); + }); + + testWithoutContext('hot reload does not need to sync assets on the first reload', () async { + final Completer onFirstLoad = Completer(); + final Completer onSecondLoad = Completer(); + + flutter.stdout.listen((String line) { + // If the asset fails to load, this message will be printed instead. + // this indicates that the devFS was not able to locate the asset + // after the hot reload. + if (line.contains('FAILED TO LOAD')) { + fail('Did not load asset: $line'); + } + if (line.contains('LOADED DATA')) { + onFirstLoad.complete(); + } + if (line.contains('SECOND DATA')) { + onSecondLoad.complete(); + } + }); + flutter.stdout.listen(print); + await flutter.run(); + await onFirstLoad.future; + + project.uncommentHotReloadPrint(); + await flutter.hotReload(); + await onSecondLoad.future; + }); + + testWithoutContext('hot restart does not need to sync assets on the first reload', () async { + final Completer onFirstLoad = Completer(); + final Completer onSecondLoad = Completer(); + + flutter.stdout.listen((String line) { + // If the asset fails to load, this message will be printed instead. + // this indicates that the devFS was not able to locate the asset + // after the hot reload. + if (line.contains('FAILED TO LOAD')) { + fail('Did not load asset: $line'); + } + if (line.contains('LOADED DATA')) { + onFirstLoad.complete(); + } + if (line.contains('SECOND DATA')) { + onSecondLoad.complete(); + } + }); + flutter.stdout.listen(print); + await flutter.run(); + await onFirstLoad.future; + + project.uncommentHotReloadPrint(); + await flutter.hotRestart(); + await onSecondLoad.future; + }); +} diff --git a/packages/flutter_tools/test/integration.shard/test_data/hot_reload_with_asset.dart b/packages/flutter_tools/test/integration.shard/test_data/hot_reload_with_asset.dart new file mode 100644 index 00000000000..25d416ccd1b --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/test_data/hot_reload_with_asset.dart @@ -0,0 +1,58 @@ +// Copyright 2014 The Flutter 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 '../test_utils.dart'; +import 'project.dart'; + +class HotReloadWithAssetProject extends Project { + @override + final String pubspec = ''' +name: test +environment: + sdk: ">=2.0.0-dev.68.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + +flutter: + assets: + - pubspec.yaml + '''; + + @override + final String main = r''' +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + final ByteData message = const StringCodec().encodeMessage('AppLifecycleState.resumed'); + await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { }); + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + rootBundle.evict('pubspec.yaml'); + rootBundle.load('pubspec.yaml').then((_) { + print('LOADED DATA'); + }, onError: (dynamic error, StackTrace stackTrace) { + print('FAILED TO LOAD'); + }); + return Container(); + } +} +'''; + + void uncommentHotReloadPrint() { + final String newMainContents = main.replaceAll( + 'LOADED DATA', + 'SECOND DATA', + ); + writeFile(fileSystem.path.join(dir.path, 'lib', 'main.dart'), newMainContents); + } +}