Refactor ios bundleid/android application process logic. (#27471)
When creating a flutter project,
Make sure: applicationid for android conforms to: https://developer.android.com/studio/build/application-id
Make sure: bundleid for ios conforms to:
https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/understanding_utis/understand_utis_conc/understand_utis_conc.html
(Not only the name,but also the organization, as it's a check for whole bundleid.)
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index ec6f616..2a13c4c 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -559,7 +559,32 @@
}
String _createAndroidIdentifier(String organization, String name) {
- return '$organization.$name'.replaceAll('_', '');
+ // Android application ID is specified in: https://developer.android.com/studio/build/application-id
+ // All characters must be alphanumeric or an underscore [a-zA-Z0-9_].
+ String tmpIdentifier = '$organization.$name';
+ final RegExp disallowed = RegExp(r'[^\w\.]');
+ tmpIdentifier = tmpIdentifier.replaceAll(disallowed, '');
+
+ // It must have at least two segments (one or more dots).
+ final List<String> segments = tmpIdentifier
+ .split('.')
+ .where((String segment) => segment.isNotEmpty)
+ .toList();
+ while (segments.length < 2) {
+ segments.add('untitled');
+ }
+
+ // Each segment must start with a letter.
+ final RegExp segmentPatternRegex = RegExp(r'^[a-zA-Z][\w]*$');
+ final List<String> prefixedSegments = segments
+ .map((String segment) {
+ if (!segmentPatternRegex.hasMatch(segment)) {
+ return 'u'+segment;
+ }
+ return segment;
+ })
+ .toList();
+ return prefixedSegments.join('.');
}
String _createPluginClassName(String name) {
@@ -569,10 +594,21 @@
String _createUTIIdentifier(String organization, String name) {
// Create a UTI (https://en.wikipedia.org/wiki/Uniform_Type_Identifier) from a base name
+ name = camelCase(name);
+ String tmpIdentifier = '$organization.$name';
final RegExp disallowed = RegExp(r'[^a-zA-Z0-9\-\.\u0080-\uffff]+');
- name = camelCase(name).replaceAll(disallowed, '');
- name = name.isEmpty ? 'untitled' : name;
- return '$organization.$name';
+ tmpIdentifier = tmpIdentifier.replaceAll(disallowed, '');
+
+ // It must have at least two segments (one or more dots).
+ final List<String> segments = tmpIdentifier
+ .split('.')
+ .where((String segment) => segment.isNotEmpty)
+ .toList();
+ while (segments.length < 2) {
+ segments.add('untitled');
+ }
+
+ return segments.join('.');
}
final Set<String> _packageDependencies = Set<String>.from(<String>[
diff --git a/packages/flutter_tools/test/commands/create_test.dart b/packages/flutter_tools/test/commands/create_test.dart
index bdb26cd..70ad746 100644
--- a/packages/flutter_tools/test/commands/create_test.dart
+++ b/packages/flutter_tools/test/commands/create_test.dart
@@ -56,7 +56,7 @@
projectDir,
<String>[],
<String>[
- 'android/app/src/main/java/com/example/flutterproject/MainActivity.java',
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
'flutter_project.iml',
'ios/Flutter/AppFrameworkInfo.plist',
@@ -74,7 +74,7 @@
projectDir,
<String>[],
<String>[
- 'android/app/src/main/java/com/example/flutterproject/MainActivity.java',
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
'flutter_project.iml',
'ios/Flutter/AppFrameworkInfo.plist',
@@ -120,7 +120,7 @@
await projectDir.absolute.childDirectory('blag').create(recursive: true);
await projectDir.absolute.childDirectory('.idea').create(recursive: true);
await _createAndAnalyzeProject(projectDir, <String>[], <String>[
- 'android/app/src/main/java/com/example/flutterproject/MainActivity.java',
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
'flutter_project.iml',
'ios/Flutter/AppFrameworkInfo.plist',
@@ -136,7 +136,7 @@
await projectDir.absolute.childDirectory('lib').create(recursive: true);
await projectDir.absolute.childDirectory('ios').create(recursive: true);
await _createAndAnalyzeProject(projectDir, <String>[], <String>[
- 'android/app/src/main/java/com/example/flutterproject/MainActivity.java',
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
'flutter_project.iml',
'ios/Flutter/AppFrameworkInfo.plist',
@@ -155,8 +155,8 @@
projectDir,
<String>[],
<String>[
- 'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java',
- 'example/android/app/src/main/java/com/example/flutterprojectexample/MainActivity.java',
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
'example/ios/Runner/AppDelegate.h',
'example/ios/Runner/AppDelegate.m',
'example/ios/Runner/main.m',
@@ -180,9 +180,9 @@
'test/flutter_project_test.dart',
],
unexpectedPaths: <String>[
- 'android/app/src/main/java/com/example/flutterproject/MainActivity.java',
- 'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java',
- 'example/android/app/src/main/java/com/example/flutterprojectexample/MainActivity.java',
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
'example/ios/Runner/AppDelegate.h',
'example/ios/Runner/AppDelegate.m',
'example/ios/Runner/main.m',
@@ -203,14 +203,14 @@
projectDir,
<String>['--no-pub', '--template=app', '--android-language=kotlin', '--ios-language=swift'],
<String>[
- 'android/app/src/main/kotlin/com/example/flutterproject/MainActivity.kt',
+ 'android/app/src/main/kotlin/com/example/flutter_project/MainActivity.kt',
'ios/Runner/AppDelegate.swift',
'ios/Runner/Runner-Bridging-Header.h',
'lib/main.dart',
'.idea/libraries/KotlinJavaRuntime.xml',
],
unexpectedPaths: <String>[
- 'android/app/src/main/java/com/example/flutterproject/MainActivity.java',
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
'ios/Runner/AppDelegate.h',
'ios/Runner/AppDelegate.m',
'ios/Runner/main.m',
@@ -227,9 +227,9 @@
'test/flutter_project_test.dart',
],
unexpectedPaths: <String>[
- 'android/app/src/main/java/com/example/flutterproject/MainActivity.java',
- 'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java',
- 'example/android/app/src/main/java/com/example/flutterprojectexample/MainActivity.java',
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
'example/ios/Runner/AppDelegate.h',
'example/ios/Runner/AppDelegate.m',
'example/ios/Runner/main.m',
@@ -251,8 +251,8 @@
projectDir,
<String>['--template=plugin'],
<String>[
- 'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java',
- 'example/android/app/src/main/java/com/example/flutterprojectexample/MainActivity.java',
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
'example/ios/Runner/AppDelegate.h',
'example/ios/Runner/AppDelegate.m',
'example/ios/Runner/main.m',
@@ -271,8 +271,8 @@
projectDir,
<String>['--no-pub', '--template=plugin', '-a', 'kotlin', '--ios-language', 'swift'],
<String>[
- 'android/src/main/kotlin/com/example/flutterproject/FlutterProjectPlugin.kt',
- 'example/android/app/src/main/kotlin/com/example/flutterprojectexample/MainActivity.kt',
+ 'android/src/main/kotlin/com/example/flutter_project/FlutterProjectPlugin.kt',
+ 'example/android/app/src/main/kotlin/com/example/flutter_project_example/MainActivity.kt',
'example/ios/Runner/AppDelegate.swift',
'example/ios/Runner/Runner-Bridging-Header.h',
'example/lib/main.dart',
@@ -282,8 +282,8 @@
'lib/flutter_project.dart',
],
unexpectedPaths: <String>[
- 'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java',
- 'example/android/app/src/main/java/com/example/flutterprojectexample/MainActivity.java',
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
'example/ios/Runner/AppDelegate.h',
'example/ios/Runner/AppDelegate.m',
'example/ios/Runner/main.m',
@@ -296,12 +296,12 @@
projectDir,
<String>['--no-pub', '--template=plugin', '--org', 'com.bar.foo'],
<String>[
- 'android/src/main/java/com/bar/foo/flutterproject/FlutterProjectPlugin.java',
- 'example/android/app/src/main/java/com/bar/foo/flutterprojectexample/MainActivity.java',
+ 'android/src/main/java/com/bar/foo/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/bar/foo/flutter_project_example/MainActivity.java',
],
unexpectedPaths: <String>[
- 'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java',
- 'example/android/app/src/main/java/com/example/flutterprojectexample/MainActivity.java',
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
],
);
}, timeout: allowForCreateFlutterProject);
@@ -312,11 +312,11 @@
<String>['--no-pub', '--template=plugin', '--project-name', 'xyz'],
<String>[
'android/src/main/java/com/example/xyz/XyzPlugin.java',
- 'example/android/app/src/main/java/com/example/xyzexample/MainActivity.java',
+ 'example/android/app/src/main/java/com/example/xyz_example/MainActivity.java',
],
unexpectedPaths: <String>[
- 'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java',
- 'example/android/app/src/main/java/com/example/flutterprojectexample/MainActivity.java',
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
],
);
}, timeout: allowForCreateFlutterProject);
@@ -518,6 +518,54 @@
Platform: _kNoColorTerminalPlatform,
}, timeout: allowForCreateFlutterProject);
+ testUsingContext('has correct application id for android and bundle id for ios', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ String tmpProjectDir = fs.path.join(tempDir.path, 'hello_flutter');
+ await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'com.example', tmpProjectDir]);
+ FlutterProject project = await FlutterProject.fromDirectory(fs.directory(tmpProjectDir));
+ expect(
+ project.ios.productBundleIdentifier,
+ 'com.example.helloFlutter'
+ );
+ expect(
+ project.android.applicationId,
+ 'com.example.hello_flutter'
+ );
+
+ tmpProjectDir = fs.path.join(tempDir.path, 'test_abc');
+ await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'abc^*.1#@', tmpProjectDir]);
+ project = await FlutterProject.fromDirectory(fs.directory(tmpProjectDir));
+ expect(
+ project.ios.productBundleIdentifier,
+ 'abc.1.testAbc'
+ );
+ expect(
+ project.android.applicationId,
+ 'abc.u1.test_abc'
+ );
+
+ tmpProjectDir = fs.path.join(tempDir.path, 'flutter_project');
+ await runner.run(<String>['create', '--template=app', '--no-pub', '--org', '#+^%', tmpProjectDir]);
+ project = await FlutterProject.fromDirectory(fs.directory(tmpProjectDir));
+ expect(
+ project.ios.productBundleIdentifier,
+ 'flutterProject.untitled'
+ );
+ expect(
+ project.android.applicationId,
+ 'flutter_project.untitled'
+ );
+ }, overrides: <Type, Generator>{
+ FlutterVersion: () => mockFlutterVersion,
+ Platform: _kNoColorTerminalPlatform,
+ }, timeout: allowForCreateFlutterProject);
+
testUsingContext('can re-gen default template over existing project', () async {
Cache.flutterRoot = '../..';
@@ -616,7 +664,7 @@
projectDir,
<String>[],
<String>[
- '.android/app/src/main/java/com/bar/foo/flutterproject/host/MainActivity.java',
+ '.android/app/src/main/java/com/bar/foo/flutter_project/host/MainActivity.java',
],
);
}, timeout: allowForRemotePubInvocation);
@@ -647,10 +695,10 @@
projectDir,
<String>['--no-pub'],
<String>[
- 'android/app/src/main/java/com/bar/foo/flutterproject/MainActivity.java',
+ 'android/app/src/main/java/com/bar/foo/flutter_project/MainActivity.java',
],
unexpectedPaths: <String>[
- 'android/app/src/main/java/com/example/flutterproject/MainActivity.java',
+ 'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
],
);
}, timeout: allowForCreateFlutterProject);
@@ -682,12 +730,12 @@
projectDir,
<String>['--no-pub', '--template=plugin'],
<String>[
- 'example/android/app/src/main/java/com/bar/foo/flutterprojectexample/MainActivity.java',
+ 'example/android/app/src/main/java/com/bar/foo/flutter_project_example/MainActivity.java',
'ios/Classes/FlutterProjectPlugin.h',
],
unexpectedPaths: <String>[
- 'example/android/app/src/main/java/com/example/flutterprojectexample/MainActivity.java',
- 'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java',
+ 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
+ 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
],
);
final FlutterProject project = await FlutterProject.fromDirectory(projectDir);