diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 8d431642fc240..6bcf91c90b0f6 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -981,6 +981,22 @@ File findBundleFile(FlutterProject project, BuildInfo buildInfo, Logger logger, fileCandidates.add(getBundleDirectory(project) .childDirectory('${buildInfo.uncapitalizedFlavor}${camelCase('_${buildInfo.modeName}')}') .childFile('app-${buildInfo.uncapitalizedFlavor}-${buildInfo.modeName}.aab')); + + // The Android Gradle plugin uses kebab-case and lowercases the first character of the flavor name + // when multiple flavor dimensions are used: + // e.g. + // flavorDimensions "dimension1","dimension2" + // productFlavors { + // foo { + // dimension "dimension1" + // } + // bar { + // dimension "dimension2" + // } + // } + fileCandidates.add(getBundleDirectory(project) + .childDirectory('${buildInfo.uncapitalizedFlavor}${camelCase('_${buildInfo.modeName}')}') + .childFile('app-${kebabCase(buildInfo.uncapitalizedFlavor!)}-${buildInfo.modeName}.aab')); } for (final File bundleFile in fileCandidates) { if (bundleFile.existsSync()) { diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart index 8bcc97e70a474..60fc81eefaa7c 100644 --- a/packages/flutter_tools/lib/src/base/utils.dart +++ b/packages/flutter_tools/lib/src/base/utils.dart @@ -26,6 +26,11 @@ String camelCase(String str) { return str; } +/// Convert `fooBar` to `foo-bar`. +String kebabCase(String str) { + return snakeCase(str, '-'); +} + final RegExp _upperRegex = RegExp(r'[A-Z]'); /// Convert `fooBar` to `foo_bar`. diff --git a/packages/flutter_tools/test/general.shard/android/gradle_find_bundle_test.dart b/packages/flutter_tools/test/general.shard/android/gradle_find_bundle_test.dart index 3d9b639af7b4f..ad0653b43c09e 100644 --- a/packages/flutter_tools/test/general.shard/android/gradle_find_bundle_test.dart +++ b/packages/flutter_tools/test/general.shard/android/gradle_find_bundle_test.dart @@ -19,6 +19,19 @@ void main() { fileSystem = MemoryFileSystem.test(); }); + testWithoutContext('Finds app bundle when flavor contains multiple dimensions in release mode', () { + final FlutterProject project = generateFakeAppBundle('fooBarRelease', 'app-foo-bar-release.aab', fileSystem); + final File bundle = findBundleFile( + project, + const BuildInfo(BuildMode.release, 'fooBar', treeShakeIcons: false), + BufferLogger.test(), + TestUsage(), + ); + + expect(bundle, isNotNull); + expect(bundle.path, '/build/app/outputs/bundle/fooBarRelease/app-foo-bar-release.aab'); + }); + testWithoutContext('Finds app bundle when flavor contains underscores in release mode', () { final FlutterProject project = generateFakeAppBundle('foo_barRelease', 'app.aab', fileSystem); final File bundle = findBundleFile( @@ -84,6 +97,19 @@ void main() { expect(bundle.path, '/build/app/outputs/bundle/release/app.aab'); }); + testWithoutContext('Finds app bundle when flavor contains multiple dimensions in debug mode', () { + final FlutterProject project = generateFakeAppBundle('fooBarDebug', 'app-foo-bar-debug.aab', fileSystem); + final File bundle = findBundleFile( + project, + const BuildInfo(BuildMode.debug, 'fooBar', treeShakeIcons: false), + BufferLogger.test(), + TestUsage(), + ); + + expect(bundle, isNotNull); + expect(bundle.path, '/build/app/outputs/bundle/fooBarDebug/app-foo-bar-debug.aab'); + }); + testWithoutContext('Finds app bundle when flavor contains underscores in debug mode', () { final FlutterProject project = generateFakeAppBundle('foo_barDebug', 'app.aab', fileSystem); final File bundle = findBundleFile( @@ -149,6 +175,19 @@ void main() { expect(bundle.path, '/build/app/outputs/bundle/debug/app.aab'); }); + testWithoutContext('Finds app bundle when flavor contains multiple dimensions in profile mode', () { + final FlutterProject project = generateFakeAppBundle('fooBarProfile', 'app-foo-bar-profile.aab', fileSystem); + final File bundle = findBundleFile( + project, + const BuildInfo(BuildMode.profile, 'fooBar', treeShakeIcons: false), + BufferLogger.test(), + TestUsage(), + ); + + expect(bundle, isNotNull); + expect(bundle.path, '/build/app/outputs/bundle/fooBarProfile/app-foo-bar-profile.aab'); + }); + testWithoutContext('Finds app bundle when flavor contains underscores in profile mode', () { final FlutterProject project = generateFakeAppBundle('foo_barProfile', 'app.aab', fileSystem); final File bundle = findBundleFile( diff --git a/packages/flutter_tools/test/general.shard/utils_test.dart b/packages/flutter_tools/test/general.shard/utils_test.dart index 8ad87e769070e..3fa94cb1c77b4 100644 --- a/packages/flutter_tools/test/general.shard/utils_test.dart +++ b/packages/flutter_tools/test/general.shard/utils_test.dart @@ -120,6 +120,17 @@ baz=qux expect(snakeCase('ABC'), equals('a_b_c')); }); + testWithoutContext('kebabCase', () async { + expect(kebabCase('abc'), equals('abc')); + expect(kebabCase('abC'), equals('ab-c')); + expect(kebabCase('aBc'), equals('a-bc')); + expect(kebabCase('aBC'), equals('a-b-c')); + expect(kebabCase('Abc'), equals('abc')); + expect(kebabCase('AbC'), equals('ab-c')); + expect(kebabCase('ABc'), equals('a-bc')); + expect(kebabCase('ABC'), equals('a-b-c')); + }); + testWithoutContext('sentenceCase', () async { expect(sentenceCase('abc'), equals('Abc')); expect(sentenceCase('ab_c'), equals('Ab_c'));