Skip to content

v2.0.0 — iOS Framework Isolation: Contacts, Calendar & Motion

Choose a tag to compare

@vietnguyentuan2019 vietnguyentuan2019 released this 14 May 23:36
e3dbdbc

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() for RawPermission now correctly dispatches through IosPermissionHandlerRegistry (previously only requestInternal() did — custom handlers were invisible to status checks)
  • mainContinuation made public so optional modules in separate Gradle projects can import the iOS threading utility
  • MyGrantManager deprecation 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


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