validateIos static method
Validate iOS configuration
Implementation
static List<VerificationResult> validateIos(
PlatformConfig localConfig,
ProjectConfig ulinkConfig,
) {
final results = <VerificationResult>[];
// Bundle Identifier match
if (localConfig.bundleIdentifier != null &&
ulinkConfig.iosBundleIdentifier != null) {
if (localConfig.bundleIdentifier != ulinkConfig.iosBundleIdentifier) {
results.add(
VerificationResult(
checkName: 'iOS Bundle Identifier Match',
status: VerificationStatus.error,
message: 'Bundle identifier mismatch',
fixSuggestion:
'Update ULink config: ${localConfig.bundleIdentifier}\n'
'Or update Info.plist: ${ulinkConfig.iosBundleIdentifier}',
details: {
'local': localConfig.bundleIdentifier,
'ulink': ulinkConfig.iosBundleIdentifier,
},
),
);
} else {
results.add(
VerificationResult(
checkName: 'iOS Bundle Identifier Match',
status: VerificationStatus.success,
message: 'Bundle identifier matches',
),
);
}
} else {
results.add(
VerificationResult(
checkName: 'iOS Bundle Identifier Match',
status: VerificationStatus.warning,
message:
'Bundle identifier not found in local config or ULink config',
fixSuggestion:
'Ensure bundle identifier is set in both Info.plist and ULink dashboard',
),
);
}
// URL Scheme match - use iOS-specific schemes for Flutter projects
final schemesToCheck = localConfig.iosUrlSchemes.isNotEmpty
? localConfig.iosUrlSchemes
: localConfig.urlSchemes;
if (ulinkConfig.iosDeeplinkSchema != null &&
ulinkConfig.iosDeeplinkSchema!.isNotEmpty) {
final expectedScheme =
ulinkConfig.iosDeeplinkSchema!.replaceAll('://', '').toLowerCase();
final hasMatchingScheme = schemesToCheck.any(
(scheme) => scheme.toLowerCase() == expectedScheme,
);
if (!hasMatchingScheme) {
results.add(
VerificationResult(
checkName: 'iOS URL Scheme Match',
status: VerificationStatus.error,
message: 'URL scheme mismatch',
fixSuggestion:
'Add URL scheme "$expectedScheme" to Info.plist CFBundleURLSchemes\n'
'Or update ULink config to match: ${schemesToCheck.isEmpty ? "(none found)" : schemesToCheck.join(", ")}',
details: {'local': schemesToCheck, 'ulink': expectedScheme},
),
);
} else {
// Check if there are extra schemes in local that don't match ULink
final extraSchemes = schemesToCheck
.where((scheme) => scheme.toLowerCase() != expectedScheme)
.toList();
if (extraSchemes.isNotEmpty) {
results.add(
VerificationResult(
checkName: 'iOS URL Scheme Match',
status: VerificationStatus.success,
message: 'URL scheme matches (ULink: $expectedScheme)',
details: {
'matched': expectedScheme,
'local': schemesToCheck,
'ulink': expectedScheme,
},
),
);
results.add(
VerificationResult(
checkName: 'iOS Extra URL Schemes',
status: VerificationStatus.warning,
message:
'Local has ${extraSchemes.length} extra URL scheme(s) not configured in ULink',
fixSuggestion:
'These schemes are in your Info.plist but not in ULink config:\n${extraSchemes.join(", ")}\n'
'If these are intentional, you can ignore this warning. Otherwise, remove them or add "$expectedScheme" to ULink.',
details: {
'extraSchemes': extraSchemes,
'ulinkScheme': expectedScheme,
},
),
);
} else {
results.add(
VerificationResult(
checkName: 'iOS URL Scheme Match',
status: VerificationStatus.success,
message: 'URL scheme matches',
),
);
}
}
} else if (schemesToCheck.isNotEmpty) {
// Local has schemes but ULink doesn't have iOS deeplink schema configured
results.add(
VerificationResult(
checkName: 'iOS URL Scheme Match',
status: VerificationStatus.warning,
message:
'Local iOS has URL schemes but ULink iOS deeplink schema is not configured',
fixSuggestion:
'Configure iOS deeplink schema in ULink dashboard to: ${schemesToCheck.first}',
details: {'local': schemesToCheck, 'ulink': '(not configured)'},
),
);
}
// Associated Domains match - local needs to match at least ONE verified domain
final iosVerifiedDomains = ulinkConfig.domains
.where((d) => d.status == 'verified')
.map((d) => d.host)
.toList();
final allUlinkDomains = ulinkConfig.domains.map((d) => d.host).toList();
if (localConfig.associatedDomains.isNotEmpty) {
// Check if any local domain matches a verified ULink domain
final matchedVerifiedDomain = localConfig.associatedDomains.firstWhere(
(localDomain) => iosVerifiedDomains.contains(localDomain),
orElse: () => '',
);
if (matchedVerifiedDomain.isNotEmpty) {
// Found a match with a verified domain
results.add(
VerificationResult(
checkName: 'iOS Associated Domain Match',
status: VerificationStatus.success,
message: 'Associated domain matches verified ULink domain',
details: {
'matched': matchedVerifiedDomain,
'local': localConfig.associatedDomains,
},
),
);
} else {
// No verified match, check if local domain exists in ULink but not verified
final unverifiedMatch = localConfig.associatedDomains.firstWhere(
(localDomain) => allUlinkDomains.contains(localDomain),
orElse: () => '',
);
if (unverifiedMatch.isNotEmpty) {
// Domain exists in ULink but is not verified
final domainStatus = ulinkConfig.domains
.firstWhere((d) => d.host == unverifiedMatch)
.status;
results.add(
VerificationResult(
checkName: 'iOS Associated Domain Match',
status: VerificationStatus.error,
message:
'Domain "$unverifiedMatch" exists in ULink but is not verified (status: $domainStatus)',
fixSuggestion:
'Complete domain verification in ULink dashboard for "$unverifiedMatch"',
details: {
'local': localConfig.associatedDomains,
'ulink': unverifiedMatch,
'status': domainStatus,
},
),
);
} else {
// Local domain doesn't exist in ULink at all
results.add(
VerificationResult(
checkName: 'iOS Associated Domain Match',
status: VerificationStatus.error,
message:
'Local domain "${localConfig.associatedDomains.first}" not found in ULink',
fixSuggestion:
'Add domain "${localConfig.associatedDomains.first}" to ULink dashboard and verify it',
details: {
'local': localConfig.associatedDomains,
'ulink': allUlinkDomains,
},
),
);
}
}
} else if (iosVerifiedDomains.isNotEmpty) {
// ULink has verified domains but local doesn't have any
results.add(
VerificationResult(
checkName: 'iOS Associated Domain Match',
status: VerificationStatus.warning,
message: 'No associated domains in local config',
fixSuggestion:
'Add at least one verified domain to your entitlements file:\n${iosVerifiedDomains.map((d) => ' applinks:$d').join('\n')}',
details: {
'local': localConfig.associatedDomains,
'ulink': iosVerifiedDomains,
},
),
);
} else if (ulinkConfig.domains.isNotEmpty) {
// ULink has domains but none are verified
results.add(
VerificationResult(
checkName: 'iOS Associated Domain Match',
status: VerificationStatus.warning,
message: 'ULink has domains but none are verified',
fixSuggestion:
'Verify your domains in the ULink dashboard to enable Universal Links',
details: {
'local': localConfig.associatedDomains,
'ulink': ulinkConfig.domains
.map((d) => '${d.host} (${d.status})')
.toList(),
},
),
);
}
// Team ID check and comparison
if (ulinkConfig.iosTeamId == null || ulinkConfig.iosTeamId!.isEmpty) {
results.add(
VerificationResult(
checkName: 'iOS Team ID',
status: VerificationStatus.warning,
message: 'Team ID not configured in ULink',
fixSuggestion:
'Add your Apple Team ID to ULink project configuration',
),
);
} else if (localConfig.teamId != null && localConfig.teamId!.isNotEmpty) {
// Compare local Team ID with ULink Team ID
if (localConfig.teamId != ulinkConfig.iosTeamId) {
results.add(
VerificationResult(
checkName: 'iOS Team ID Match',
status: VerificationStatus.error,
message: 'Team ID mismatch',
fixSuggestion: 'Update ULink Team ID to: ${localConfig.teamId}\n'
'Or update your Xcode project to use: ${ulinkConfig.iosTeamId}',
details: {
'local': localConfig.teamId,
'ulink': ulinkConfig.iosTeamId,
},
),
);
} else {
results.add(
VerificationResult(
checkName: 'iOS Team ID Match',
status: VerificationStatus.success,
message: 'Team ID matches',
),
);
}
} else {
results.add(
VerificationResult(
checkName: 'iOS Team ID',
status: VerificationStatus.success,
message: 'Team ID is configured in ULink',
details: {'ulink': ulinkConfig.iosTeamId},
),
);
}
return results;
}