diff --git a/.gitignore b/.gitignore index 93ace237..720555f3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,13 @@ example/pubspec.lock pubspec.lock example/android/app/property* +# Local Claude Code settings +.claude/settings.local.json + +# Generated SDK review reports +sdk-review-*.html + +# SPM build artifacts +.build/ +.swiftpm/ + diff --git a/doc/Integrate-iOS-SPM.md b/doc/Integrate-iOS-SPM.md new file mode 100644 index 00000000..7c3b1d0d --- /dev/null +++ b/doc/Integrate-iOS-SPM.md @@ -0,0 +1,124 @@ +## πŸ“¦ iOS Integration via Swift Package Manager (SPM) + +Flutter introduced Swift Package Manager support as an **opt-in** feature in Flutter 3.24, and it became the **default** package manager in Flutter 3.44. The CleverTap Flutter plugin from v4.2.0 ships a `Package.swift` alongside its podspec, so both package managers resolve the same native SDK version (`CleverTap-iOS-SDK 7.8.1`). + +> **Minimum requirements for SPM integration** +> - Flutter 3.24+ (opt-in) - enabled by default from Flutter 3.44 +> - Xcode 15+ (required for `swift-tools-version: 5.9`) +> - iOS deployment target: 13.0+ +> +> If you are on Flutter < 3.24, the plugin continues to work via CocoaPods - no action required. + +--- + +### How it works + +The plugin ships a Swift package that pins `CleverTap-iOS-SDK 7.8.1`. When your app is built with SPM enabled, Flutter resolves it automatically - you don't add anything to your app's own `Package.swift` or edit any plugin files. + +--- + +### Enabling SPM in your Flutter app + +SPM is opt-in on Flutter 3.24–3.43 (and on by default from 3.44). There is **no** `flutter run`/`flutter build` flag and **no** `Info.plist` key for this - you enable it through Flutter configuration. + +**Per project (recommended for testing)** - add to your app's `pubspec.yaml`: + +```yaml +flutter: + config: + enable-swift-package-manager: true +``` + +**Globally for your machine:** + +```bash +flutter config --enable-swift-package-manager +``` + +To turn it back off, use `enable-swift-package-manager: false` in `pubspec.yaml`, or `flutter config --no-enable-swift-package-manager` globally. + +--- + +### Migrating an existing CocoaPods app to SPM + +Once SPM is enabled, Flutter migrates the Xcode project automatically the next time you build or run. To migrate cleanly on an app that already uses CocoaPods: + +**1. Enable SPM** (per-project block above, or global flag). + +**2. Clear the existing CocoaPods state** (run from your app root): + +```bash +flutter clean +cd ios +rm -rf Pods Podfile.lock .symlinks build +rm -rf ~/Library/Developer/Xcode/DerivedData/Runner-* +cd .. +``` + +**3. Re-resolve and trigger the migration:** + +```bash +flutter pub get +flutter build ios --config-only # or: flutter run +``` + +On this build Flutter adds a `FlutterGeneratedPluginSwiftPackage` to the Xcode project and resolves every plugin that ships a `Package.swift` (including `clevertap_plugin`) via SPM. Plugins that only ship a podspec stay on CocoaPods - a hybrid setup is expected. + +**4. Verify SPM took over.** Open `ios/Runner.xcworkspace` and confirm: +- **Package Dependencies** lists `clevertap_plugin` and, transitively, `clevertap-ios-sdk` at `7.8.1`. +- The regenerated `Podfile.lock` no longer contains a `clevertap_plugin` pod entry. + +**5. Build and run** on a simulator/device and smoke-test CleverTap initialization plus a `recordEvent` to confirm native symbols link. + +--- + +### AppDelegate changes when using SPM + +When Flutter resolves the plugin via SPM, the module name changes. Use conditional imports in your `AppDelegate` to support both CocoaPods and SPM builds: + +**Objective-C** + +```objc +#if __has_include() +#import +#else +#import "CleverTap.h" +#endif + +#if __has_include() +#import +#else +#import "CleverTapPlugin.h" +#endif +``` + +**Swift** + +```swift +import CleverTapSDK +import clevertap_plugin +``` + +These guards ensure your app compiles correctly regardless of which package manager resolved the dependency. + +--- + +### CocoaPods vs SPM + +| | CocoaPods | SPM | +|---|---|---| +| SDK version pinned | `7.8.1` | `7.8.1` | +| Deployment target | `9.0` | `13.0` | +| Opt-in required | No | Yes on Flutter 3.24–3.43; default from 3.44 | + +Both package managers resolve the same CleverTap iOS SDK version to ensure no drift between the two integration paths. + +--- + +### Troubleshooting + +- **Xcode doesn't resolve the package**: Run `flutter pub get`, then in Xcode go to **File β†’ Packages β†’ Resolve Package Versions**. +- **Automatic migration didn't add the package**: In Xcode, add it manually - **Package Dependencies β†’ Add Local…** β†’ select `ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage` and add it to the `Runner` target. Then under **Product β†’ Scheme β†’ Edit Scheme β†’ Build β†’ Pre-actions**, add a Run Script (with build settings from `Runner`): `"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare`. +- **Duplicate symbol errors**: Don't use both CocoaPods and SPM for the same plugin at once. If a `Podfile` exists and SPM is enabled, Flutter prefers SPM for plugins that support it. +- **Build fails with missing headers**: Verify your deployment target is `13.0` or higher in both your Xcode project and `Podfile`/`Package.swift`. +- **Stale CocoaPods references after migrating**: If the build still runs `[CP]` Pods build phases (or duplicate-embeds a framework) for a plugin that has moved to SPM, run `pod deintegrate` in your app's `ios/` directory, then `flutter clean && flutter build ios` so Flutter re-integrates cleanly. Use this only as a recovery step - it is not part of the normal migration flow. Flutter regenerates the CocoaPods integration on every build, so a manual `pod deintegrate` is otherwise undone on the next build. diff --git a/example/ios/.gitignore b/example/ios/.gitignore index e96ef602..61fa3c45 100644 --- a/example/ios/.gitignore +++ b/example/ios/.gitignore @@ -22,6 +22,7 @@ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ Flutter/flutter_export_environment.sh +Flutter/ephemeral/ ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* diff --git a/example/ios/Runner/AppDelegate.m b/example/ios/Runner/AppDelegate.m index d76db9f9..f9b28f8a 100644 --- a/example/ios/Runner/AppDelegate.m +++ b/example/ios/Runner/AppDelegate.m @@ -2,9 +2,18 @@ #import "GeneratedPluginRegistrant.h" #import +#if __has_include() +#import +#else #import "CleverTap.h" +#endif +#if __has_include() +#import +#import +#else #import "CleverTapPlugin.h" #import "CleverTapPluginCustomTemplates.h" +#endif @implementation AppDelegate diff --git a/ios/.gitignore b/ios/.gitignore index aa479fd3..fe343d53 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -34,4 +34,6 @@ Icon? .tags* /Flutter/Generated.xcconfig -/Flutter/flutter_export_environment.sh \ No newline at end of file +/Flutter/flutter_export_environment.sh + +.swiftpm/ \ No newline at end of file diff --git a/ios/Assets/.gitkeep b/ios/Assets/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/ios/clevertap_plugin.podspec b/ios/clevertap_plugin.podspec index 7777a58b..85dd589b 100644 --- a/ios/clevertap_plugin.podspec +++ b/ios/clevertap_plugin.podspec @@ -10,8 +10,8 @@ Pod::Spec.new do |s| s.license = { :file => '../LICENSE' } s.author = { "CleverTap" => "http://www.clevertap.com" } s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.public_header_files = 'Classes/**/*.h' + s.source_files = 'clevertap_plugin/Sources/clevertap_plugin/**/*.{h,m}' + s.public_header_files = 'clevertap_plugin/Sources/clevertap_plugin/include/clevertap_plugin/*.h' s.dependency 'Flutter' s.dependency 'CleverTap-iOS-SDK', '7.7.1' s.ios.deployment_target = '9.0' diff --git a/ios/clevertap_plugin/Package.swift b/ios/clevertap_plugin/Package.swift new file mode 100644 index 00000000..ca8ccc2c --- /dev/null +++ b/ios/clevertap_plugin/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version: 5.9 +import PackageDescription + +let package = Package( + name: "clevertap_plugin", + platforms: [ + .iOS("13.0") + ], + products: [ + .library(name: "clevertap-plugin", targets: ["clevertap_plugin"]) + ], + dependencies: [ + .package( + url: "https://github.com/CleverTap/clevertap-ios-sdk", + exact: "7.8.1" + ) + ], + targets: [ + .target( + name: "clevertap_plugin", + dependencies: [ + .product(name: "CleverTapSDK", package: "clevertap-ios-sdk") + ], + cSettings: [ + .headerSearchPath("include/clevertap_plugin") + ] + ) + ] +) diff --git a/ios/Classes/CleverTapPlugin.m b/ios/clevertap_plugin/Sources/clevertap_plugin/CleverTapPlugin.m similarity index 97% rename from ios/Classes/CleverTapPlugin.m rename to ios/clevertap_plugin/Sources/clevertap_plugin/CleverTapPlugin.m index ad6e2210..6642e0da 100644 --- a/ios/Classes/CleverTapPlugin.m +++ b/ios/clevertap_plugin/Sources/clevertap_plugin/CleverTapPlugin.m @@ -1,22 +1,103 @@ #import + +#if __has_include() +#import +#else #import "CleverTap.h" +#endif + #import "CleverTapPlugin.h" + +#if __has_include() +#import +#else #import "CleverTap+Inbox.h" +#endif + +#if __has_include() +#import +#else #import "CleverTapUTMDetail.h" +#endif + +#if __has_include() +#import +#else #import "CleverTapEventDetail.h" +#endif + +#if __has_include() +#import +#else #import "CleverTapSyncDelegate.h" +#endif + +#if __has_include() +#import +#else #import "CleverTap+DisplayUnit.h" +#endif + +#if __has_include() +#import +#else #import "CleverTap+FeatureFlags.h" +#endif + +#if __has_include() +#import +#else #import "CleverTap+ProductConfig.h" +#endif + +#if __has_include() +#import +#else #import "CleverTapPushNotificationDelegate.h" +#endif + +#if __has_include() +#import +#else #import "CleverTapInAppNotificationDelegate.h" +#endif + +#if __has_include() +#import +#else #import "CleverTap+InAppNotifications.h" +#endif + +#if __has_include() +#import +#else #import "CleverTap+PushPermission.h" +#endif + +#if __has_include() +#import +#else #import "CTLocalInApp.h" +#endif + +#if __has_include() +#import +#else #import "CleverTap+CTVar.h" +#endif + +#if __has_include() +#import +#else #import "CTVar.h" +#endif + +#if __has_include() +#import +#else #import "CTTemplateContext.h" +#endif @interface CleverTapPlugin () diff --git a/ios/Classes/CleverTapPluginAppFunctionPresenter.m b/ios/clevertap_plugin/Sources/clevertap_plugin/CleverTapPluginAppFunctionPresenter.m similarity index 100% rename from ios/Classes/CleverTapPluginAppFunctionPresenter.m rename to ios/clevertap_plugin/Sources/clevertap_plugin/CleverTapPluginAppFunctionPresenter.m diff --git a/ios/Classes/CleverTapPluginCustomTemplates.m b/ios/clevertap_plugin/Sources/clevertap_plugin/CleverTapPluginCustomTemplates.m similarity index 89% rename from ios/Classes/CleverTapPluginCustomTemplates.m rename to ios/clevertap_plugin/Sources/clevertap_plugin/CleverTapPluginCustomTemplates.m index d5eaae46..da35cdde 100644 --- a/ios/Classes/CleverTapPluginCustomTemplates.m +++ b/ios/clevertap_plugin/Sources/clevertap_plugin/CleverTapPluginCustomTemplates.m @@ -9,8 +9,18 @@ #import "CleverTapPluginCustomTemplates.h" #import "CleverTapPluginTemplatePresenter.h" #import "CleverTapPluginAppFunctionPresenter.h" + +#if __has_include() +#import +#else #import "CTJsonTemplateProducer.h" +#endif + +#if __has_include() +#import +#else #import "CTCustomTemplatesManager.h" +#endif @implementation CleverTapPluginCustomTemplates diff --git a/ios/Classes/CleverTapPluginTemplatePresenter.m b/ios/clevertap_plugin/Sources/clevertap_plugin/CleverTapPluginTemplatePresenter.m similarity index 100% rename from ios/Classes/CleverTapPluginTemplatePresenter.m rename to ios/clevertap_plugin/Sources/clevertap_plugin/CleverTapPluginTemplatePresenter.m diff --git a/ios/Classes/CleverTapPlugin.h b/ios/clevertap_plugin/Sources/clevertap_plugin/include/clevertap_plugin/CleverTapPlugin.h similarity index 100% rename from ios/Classes/CleverTapPlugin.h rename to ios/clevertap_plugin/Sources/clevertap_plugin/include/clevertap_plugin/CleverTapPlugin.h diff --git a/ios/Classes/CleverTapPluginAppFunctionPresenter.h b/ios/clevertap_plugin/Sources/clevertap_plugin/include/clevertap_plugin/CleverTapPluginAppFunctionPresenter.h similarity index 100% rename from ios/Classes/CleverTapPluginAppFunctionPresenter.h rename to ios/clevertap_plugin/Sources/clevertap_plugin/include/clevertap_plugin/CleverTapPluginAppFunctionPresenter.h diff --git a/ios/Classes/CleverTapPluginCustomTemplates.h b/ios/clevertap_plugin/Sources/clevertap_plugin/include/clevertap_plugin/CleverTapPluginCustomTemplates.h similarity index 100% rename from ios/Classes/CleverTapPluginCustomTemplates.h rename to ios/clevertap_plugin/Sources/clevertap_plugin/include/clevertap_plugin/CleverTapPluginCustomTemplates.h diff --git a/ios/Classes/CleverTapPluginTemplatePresenter.h b/ios/clevertap_plugin/Sources/clevertap_plugin/include/clevertap_plugin/CleverTapPluginTemplatePresenter.h similarity index 100% rename from ios/Classes/CleverTapPluginTemplatePresenter.h rename to ios/clevertap_plugin/Sources/clevertap_plugin/include/clevertap_plugin/CleverTapPluginTemplatePresenter.h diff --git a/pubspec.yaml b/pubspec.yaml index 448f14b9..a3cb56a4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,6 +28,7 @@ flutter: pluginClass: CleverTapPlugin ios: pluginClass: CleverTapPlugin + swiftPackageName: clevertap_plugin web: pluginClass: CleverTapPluginWeb fileName: clevertap_plugin_web.dart