Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions lib/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ const _copyrightKey = 'copyright_notice';
const _languageKey = 'lang';
const _executableKey = 'exe_name';
const _overrideOldPackageKey = 'override_old_package';
const _customDirPath = 'custom_dir_path';
const _host = 'host';

// ! Directory Paths
// ? Android
Expand Down Expand Up @@ -128,9 +130,9 @@ final _majorTaskDoneLine = '━' * _outputLength;
const _androidKotlinMainActivityTemplate = '''
package {{packageName}}

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity : FlutterActivity()
class MainActivity : FlutterFragmentActivity()
Comment on lines +133 to +135
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Kotlin template was changed to use FlutterFragmentActivity instead of FlutterActivity, but the Java template (lines 141-143) still uses FlutterActivity. This creates an inconsistency between the two templates. Both should use the same base class, or the difference should be intentional and documented.

If this change is intentional and related to supporting microfrontends, consider:

  1. Updating the Java template to also use FlutterFragmentActivity
  2. Or explaining why the templates differ in their respective use cases

Copilot uses AI. Check for mistakes.
''';

const _androidJavaMainActivityTemplate = '''
Expand Down
80 changes: 66 additions & 14 deletions lib/platforms/android.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ void _setAndroidConfigurations(dynamic androidConfig) {

final androidConfigMap = Map<String, dynamic>.from(androidConfig);

_setAndroidAppName(androidConfigMap[_appNameKey]);
_setAndroidPackageName(androidConfigMap[_packageNameKey]);
_setAndroidAppName(
androidConfigMap[_appNameKey], androidConfigMap[_customDirPath], androidConfigMap[_host]);
_setAndroidPackageName(
androidConfigMap[_packageNameKey], androidConfigMap[_customDirPath]);
_createNewMainActivity(
lang: androidConfigMap[_languageKey],
packageName: androidConfigMap[_packageNameKey],
overrideOldPackage: androidConfigMap[_overrideOldPackageKey],
customDirPath: androidConfigMap[_customDirPath],
);
} on _PackageRenameException catch (e) {
_logger
Expand All @@ -28,21 +31,34 @@ void _setAndroidConfigurations(dynamic androidConfig) {
}
}

Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new customDirPath and host parameters lack documentation. Consider adding documentation comments to explain:

  1. What customDirPath is used for (custom directory path for melos/microfrontend support)
  2. What host is used for (appears to set the android:host attribute in AndroidManifest.xml)
  3. Expected format/values for each parameter

Example:

/// Sets the Android app name in the AndroidManifest.xml file.
/// 
/// [appName] - The display name for the Android application
/// [customDirPath] - Optional custom directory path for the android/app folder, 
///                   used in melos/microfrontend setups
/// [host] - Optional host value to set in the AndroidManifest.xml
void _setAndroidAppName(dynamic appName, String? customDirPath, dynamic host) {
Suggested change
/// Sets the Android app name and optionally the android:host attribute in the AndroidManifest.xml file.
///
/// [appName] - The display name for the Android application. Must be a non-empty String.
/// [customDirPath] - Optional custom directory path for the android/app folder, used in melos/microfrontend setups.
/// If provided and non-empty, replaces the default android/app directory path when locating the manifest file.
/// [host] - Optional host value to set in the AndroidManifest.xml. If provided and non-empty, sets the android:host attribute.

Copilot uses AI. Check for mistakes.
void _setAndroidAppName(dynamic appName) {
void _setAndroidAppName(dynamic appName, String? customDirPath, dynamic host) {
try {
if (appName == null) return;
if (appName is! String) throw _PackageRenameErrors.invalidAppName;

final androidManifestFile = File(_androidMainManifestFilePath);
final androidMainManifestFilePath =
(customDirPath is String && customDirPath.isNotEmpty)
? _androidMainManifestFilePath.replaceAll(
_androidAppDirPath, customDirPath)
: _androidMainManifestFilePath;
Comment on lines +39 to +43
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path replacement logic (customDirPath is String && customDirPath.isNotEmpty) ? path.replaceAll(...) : path is duplicated multiple times throughout the Android and iOS implementations. Consider extracting this into a helper function to improve maintainability:

String _getCustomPath(String defaultPath, String? customDirPath, String defaultDir) {
  return (customDirPath is String && customDirPath.isNotEmpty)
      ? defaultPath.replaceAll(defaultDir, customDirPath)
      : defaultPath;
}

This would simplify all the path calculations and make the code more maintainable.

Copilot uses AI. Check for mistakes.

final androidManifestFile = File(androidMainManifestFilePath);
if (!androidManifestFile.existsSync()) {
throw _PackageRenameErrors.androidMainManifestNotFound;
}

final androidManifestString = androidManifestFile.readAsStringSync();
final newLabelAndroidManifestString = androidManifestString.replaceAll(
String newLabelAndroidManifestString = androidManifestString.replaceAll(
RegExp('android:label="(.*)"'),
'android:label="$appName"',
);
if (host is String && host.isNotEmpty) {
newLabelAndroidManifestString = newLabelAndroidManifestString.replaceAll(
RegExp('android:host="(.*)"'),
'android:host="$host"',
);
_logger.i('Android Host set to: `$host` (main AndroidManifest.xml)');
}

androidManifestFile.writeAsStringSync(newLabelAndroidManifestString);

Expand All @@ -61,15 +77,24 @@ void _setAndroidAppName(dynamic appName) {
}
}

void _setAndroidPackageName(dynamic packageName) {
void _setAndroidPackageName(dynamic packageName, String? customDirPath) {
try {
if (packageName == null) return;
if (packageName is! String) throw _PackageRenameErrors.invalidPackageName;

final androidManifestFilePaths = [
_androidMainManifestFilePath,
_androidDebugManifestFilePath,
_androidProfileManifestFilePath,
(customDirPath is String && customDirPath.isNotEmpty)
? _androidMainManifestFilePath.replaceAll(
_androidAppDirPath, customDirPath)
: _androidMainManifestFilePath,
(customDirPath is String && customDirPath.isNotEmpty)
? _androidDebugManifestFilePath.replaceAll(
_androidAppDirPath, customDirPath)
: _androidDebugManifestFilePath,
(customDirPath is String && customDirPath.isNotEmpty)
? _androidProfileManifestFilePath.replaceAll(
_androidAppDirPath, customDirPath)
: _androidProfileManifestFilePath,
];

_setManifestPackageName(
Expand All @@ -78,8 +103,15 @@ void _setAndroidPackageName(dynamic packageName) {
);

_setBuildGradlePackageName(
buildGradleFilePath: _androidAppLevelBuildGradleFilePath,
kotlinBuildGradleFilePath: _androidAppLevelKotlinBuildGradleFilePath,
buildGradleFilePath: (customDirPath is String && customDirPath.isNotEmpty)
? _androidAppLevelBuildGradleFilePath.replaceAll(
_androidAppDirPath, customDirPath)
: _androidAppLevelBuildGradleFilePath,
kotlinBuildGradleFilePath:
(customDirPath is String && customDirPath.isNotEmpty)
? _androidAppLevelKotlinBuildGradleFilePath.replaceAll(
_androidAppDirPath, customDirPath)
: _androidAppLevelKotlinBuildGradleFilePath,
packageName: packageName,
);
} on _PackageRenameException catch (e) {
Expand Down Expand Up @@ -183,6 +215,7 @@ void _createNewMainActivity({
required dynamic lang,
required dynamic packageName,
required dynamic overrideOldPackage,
String? customDirPath,
}) {
try {
if (packageName == null) return;
Expand All @@ -205,7 +238,10 @@ void _createNewMainActivity({

if (overrideOldPackage == null) {
final packageDirs = packageName.replaceAll('.', '/');
final langDir = '$_androidMainDirPath/$lang';
final dirPath = (customDirPath is String && customDirPath.isNotEmpty)
? '${_androidMainDirPath.replaceAll(_androidAppDirPath, customDirPath)}/$lang'
: '$_androidMainDirPath/$lang';
final langDir = dirPath;

final mainActivityFile = File(
'$langDir/$packageDirs/MainActivity.$fileExtension',
Expand Down Expand Up @@ -240,7 +276,10 @@ void _createNewMainActivity({
// to new package directory structure
final oldPackageDirs = overrideOldPackage.replaceAll('.', '/');
final newPackageDirs = packageName.replaceAll('.', '/');
final langDir = '$_androidMainDirPath/$lang';
final dirPath = (customDirPath is String && customDirPath.isNotEmpty)
? '${_androidMainDirPath.replaceAll(_androidAppDirPath, customDirPath)}/$lang'
: '$_androidMainDirPath/$lang';
final langDir = dirPath;

final oldMainActivityDir = Directory('$langDir/$oldPackageDirs');
if (!oldMainActivityDir.existsSync()) {
Expand Down Expand Up @@ -294,7 +333,20 @@ void _createNewMainActivity({
}
}

_logger.i('New MainActivity.${lang == 'kotlin' ? 'kt' : 'java'} created');
if (overrideOldPackage == null) {
_logger.i(
'New MainActivity.${lang == 'kotlin' ? 'kt' : 'java'} created at: '
'${(customDirPath is String && customDirPath.isNotEmpty) ? customDirPath : _androidAppDirPath}/'
'src/main/$lang/${packageName.replaceAll('.', '/')}',
);
} else {
_logger.i(
'MainActivity.${lang == 'kotlin' ? 'kt' : 'java'} moved from package: '
'`$overrideOldPackage` to `$packageName` at: '
'${(customDirPath is String && customDirPath.isNotEmpty) ? customDirPath : _androidAppDirPath}/'
'src/main/$lang/${packageName.replaceAll('.', '/')}',
);
}
} on _PackageRenameException catch (e) {
_logger
..e('${e.message}ERR Code: ${e.code}')
Expand Down
28 changes: 19 additions & 9 deletions lib/platforms/ios.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ void _setIOSConfigurations(dynamic iosConfig) {
if (iosConfig is! Map) throw _PackageRenameErrors.invalidIOSConfig;

final iosConfigMap = Map<String, dynamic>.from(iosConfig);
final customDirPath = iosConfigMap[_customDirPath];

_setIOSDisplayName(iosConfigMap[_appNameKey]);
_setIOSBundleName(iosConfigMap[_bundleNameKey]);
_setIOSPackageName(iosConfigMap[_packageNameKey]);
_setIOSDisplayName(iosConfigMap[_appNameKey], customDirPath: customDirPath);
_setIOSBundleName(iosConfigMap[_bundleNameKey], customDirPath: customDirPath);
_setIOSPackageName(iosConfigMap[_packageNameKey], customDirPath: customDirPath);
} on _PackageRenameException catch (e) {
_logger
..e('${e.message}ERR Code: ${e.code}')
Expand All @@ -24,12 +25,15 @@ void _setIOSConfigurations(dynamic iosConfig) {
}
}

void _setIOSDisplayName(dynamic appName) {
void _setIOSDisplayName(dynamic appName, {String? customDirPath}) {
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new customDirPath parameter lacks documentation. Consider adding documentation comments to explain what it's used for and expected format:

/// Sets the iOS display name in the Info.plist file.
/// 
/// [appName] - The display name for the iOS application
/// [customDirPath] - Optional custom directory path for the ios folder, 
///                   used in melos/microfrontend setups
void _setIOSDisplayName(dynamic appName, {String? customDirPath}) {

Copilot uses AI. Check for mistakes.
try {
if (appName == null) return;
if (appName is! String) throw _PackageRenameErrors.invalidAppName;

final iosInfoPlistFile = File(_iosInfoPlistFilePath);
final iosInfoPlistFilePath = (customDirPath is String && customDirPath.isNotEmpty)
? _iosInfoPlistFilePath.replaceFirst(_iosDirPath, customDirPath)
: _iosInfoPlistFilePath;
Comment on lines +33 to +35
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistency in path replacement methods: iOS uses replaceFirst while Android uses replaceAll for the same purpose (custom directory path replacement). Since directory paths like _iosDirPath ('ios') and _androidAppDirPath ('android/app') should only appear once at the beginning of the path, both should use the same method for consistency.

Consider using replaceFirst consistently across both platforms, as it's more precise and avoids potential issues if the directory name appears elsewhere in the path.

Copilot uses AI. Check for mistakes.
final iosInfoPlistFile = File(iosInfoPlistFilePath);
if (!iosInfoPlistFile.existsSync()) {
throw _PackageRenameErrors.iosInfoPlistNotFound;
}
Expand Down Expand Up @@ -57,7 +61,7 @@ void _setIOSDisplayName(dynamic appName) {
}
}

void _setIOSBundleName(dynamic bundleName) {
void _setIOSBundleName(dynamic bundleName, {String? customDirPath}) {
try {
if (bundleName == null) return;
if (bundleName is! String) throw _PackageRenameErrors.invalidBundleName;
Expand All @@ -68,7 +72,10 @@ void _setIOSBundleName(dynamic bundleName) {
);
}

final iosInfoPlistFile = File(_iosInfoPlistFilePath);
final iosInfoPlistFilePath = (customDirPath is String && customDirPath.isNotEmpty)
? _iosInfoPlistFilePath.replaceFirst(_iosDirPath, customDirPath)
: _iosInfoPlistFilePath;
final iosInfoPlistFile = File(iosInfoPlistFilePath);
if (!iosInfoPlistFile.existsSync()) {
throw _PackageRenameErrors.iosInfoPlistNotFound;
}
Expand Down Expand Up @@ -96,12 +103,15 @@ void _setIOSBundleName(dynamic bundleName) {
}
}

void _setIOSPackageName(dynamic packageName) {
void _setIOSPackageName(dynamic packageName, {String? customDirPath}) {
try {
if (packageName == null) return;
if (packageName is! String) throw _PackageRenameErrors.invalidPackageName;

final iosProjectFile = File(_iosProjectFilePath);
final iosProjectFilePath = (customDirPath is String && customDirPath.isNotEmpty)
? _iosProjectFilePath.replaceFirst(_iosDirPath, customDirPath)
: _iosProjectFilePath;
final iosProjectFile = File(iosProjectFilePath);
if (!iosProjectFile.existsSync()) {
throw _PackageRenameErrors.iosProjectFileNotFound;
}
Expand Down