v2.0.0 — iOS Framework Isolation: Contacts, Calendar & Motion
What's New
v2.0.0 is a major architectural release that permanently resolves App Store ITMS-90683 rejections caused by Grant linking Contacts.framework, EventKit.framework, and CoreMotion.framework into every consumer binary — even for apps that never request those permissions.
The Problem This Fixes
Previously, adding grant-core as a dependency caused Apple's static analyzer to require NSContactsUsageDescription, NSCalendarsUsageDescription, and NSMotionUsageDescription in Info.plist — even for apps that only use LOCATION, BLUETOOTH, or NOTIFICATION.
The root cause: Kotlin/Native's ObjC interop toolchain registers framework names as metadata in the .klib at compile time, before DCE or the linker runs. Apple's static analyzer reads this compile-time metadata directly. No amount of -weak_framework flags, by lazy initialization, or handler-pattern refactoring could fix this — the framework name string was baked into grant-core.klib the moment the source file was compiled.
The only complete fix is at the dependency graph level: move the source file into a separate artifact that consumers simply don't add unless they need it.
Breaking Changes — iOS Only
Apps using AppGrant.CONTACTS, AppGrant.READ_CONTACTS, AppGrant.CALENDAR, AppGrant.READ_CALENDAR, or AppGrant.MOTION on iOS must:
1. Add the corresponding optional module:
// shared/build.gradle.kts
commonMain.dependencies {
implementation("dev.brewkits:grant-core:2.0.0")
// Add only what your app uses:
implementation("dev.brewkits:grant-contacts:2.0.0")
implementation("dev.brewkits:grant-calendar:2.0.0")
implementation("dev.brewkits:grant-motion:2.0.0")
}2. Call initialize() once on iOS app start:
// iosMain — call once, e.g. in ApplicationDelegate
GrantContacts.initialize()
GrantCalendar.initialize()
GrantMotion.initialize()Android: zero changes required.
New Modules
| Artifact | Isolates | iOS framework |
|---|---|---|
dev.brewkits:grant-contacts:2.0.0 |
AppGrant.CONTACTS, AppGrant.READ_CONTACTS |
Contacts.framework |
dev.brewkits:grant-calendar:2.0.0 |
AppGrant.CALENDAR, AppGrant.READ_CALENDAR |
EventKit.framework |
dev.brewkits:grant-motion:2.0.0 |
AppGrant.MOTION |
CoreMotion.framework |
Apps that don't add an optional module never link the corresponding framework. Apple's scanner finds no metadata and does not require the NSUsageDescription key. No workaround keys in Info.plist needed.
Additional Fixes
checkStatus()forRawPermissionnow correctly dispatches throughIosPermissionHandlerRegistry(previously onlyrequestInternal()did — custom handlers were invisible to status checks)mainContinuationmade public so optional modules in separate Gradle projects can import the iOS threading utilityMyGrantManagerdeprecation message updated to reflect v2.0.0 rename
Test Suite
1131 tests, 100% pass rate — Android JVM (Robolectric) + iOS Simulator arm64:
| Module | Android | iOS | Total |
|---|---|---|---|
grant-core |
416 | 379 | 795 |
grant-contacts |
54 | 54 | 108 |
grant-calendar |
60 | 60 | 120 |
grant-motion |
52 | 52 | 104 |
grant-compose |
1 | 1 | 2 |
grant-core-koin |
1 | 1 | 2 |
| Total | 584 | 547 | 1131 |
Each new module includes unit, integration, system, performance (1 000 sequential + parallel requests), stress (300 concurrent), regression, and security tests.
Documentation
docs/ios/APPLE_FRAMEWORK_LINKING_ISSUE.md— completely rewritten to explain the three build-stage layers (compile-time ObjC interop metadata, DCE, linker flags) and why only a dependency graph boundary provides a complete fixdocs/MIGRATION_GUIDE.md— v2.0.0 migration sectionROADMAP.md— v2.1.0 planning: opt-in Builder DSL for granular DCE across all permissions (design credit: @RoryKelly, PR #39)
What's Next — v2.1.0
The opt-in GrantFactory.create { location(); bluetooth() } Builder DSL from PR #39 will be incorporated in v2.1.0. This will allow DCE to strip any unused handler (including Camera, Location, Bluetooth) for apps that want to minimize binary size beyond what module isolation alone provides.
Full Changelog: v1.4.2...v2.0.0