Skip to content
Merged
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
22 changes: 22 additions & 0 deletions .github/scripts/check-single-copy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
# Counts the Mach-O images in an .app that define each class and expects exactly 1 each. Exits non-zero
# when an SDK is linked twice, for example from both CocoaPods and Swift Package Manager.
# Usage: .github/scripts/check-single-copy.sh path/to/Runner.app
set -u
APP="${1}"
bins=("${APP}/$(basename "${APP}" .app)" "${APP}/$(basename "${APP}" .app).debug.dylib")
while IFS= read -r fw; do
bins+=("${fw}/$(basename "${fw}" .framework)")
done < <(find "${APP}/Frameworks" -maxdepth 1 -name '*.framework' 2>/dev/null || true)

status=0
for cls in MParticle RoktEmbeddedView MPKitRokt; do
hits=()
for b in "${bins[@]}"; do
[[ -f ${b} ]] || continue
if nm -U "${b}" 2>/dev/null | grep -qE "_OBJC_CLASS_\\\$_${cls}\$"; then hits+=("$(basename "${b}")"); fi
done
echo "${cls}: ${#hits[@]} [${hits[*]-}]"
[[ ${#hits[@]} -eq 1 ]] || status=1
done
exit "${status}"
91 changes: 75 additions & 16 deletions .github/workflows/build-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
name: Build iOS flutter app (CocoaPods)
runs-on: macos-15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

Check warning on line 14 in .github/workflows/build-ios.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

artipacked

build-ios.yml:14: credential persistence through GitHub Actions artifacts: does not set persist-credentials: false
- name: Set up Xcode
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
with:
Expand All @@ -34,13 +34,65 @@
- name: Test
working-directory: example
run: flutter test
- name: Switch example to CocoaPods
working-directory: example/ios
run: |
# The example gets the kits from Swift Package Manager. Remove every Swift package from the
# project and add the kits as pods instead, otherwise the app links a second copy of the SDK.
# That includes Flutter's generated packages, which Flutter versions before 3.24 never generate.
ruby -rxcodeproj -e '
project = Xcodeproj::Project.open("Runner.xcodeproj")
target = project.targets.find { |t| t.name == "Runner" }
target.package_product_dependencies.dup.each do |dep|
target.frameworks_build_phase.files.select { |f| f.product_ref == dep }.each(&:remove_from_project)
dep.remove_from_project
end
project.root_object.package_references.dup.each(&:remove_from_project)
project.files.select { |f| f.path.to_s.start_with?("Flutter/ephemeral/Packages/") }.each(&:remove_from_project)
project.save'
cat > Podfile <<'PODFILE'
platform :ios, '15.6'
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release }

def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup

target 'Runner' do
use_frameworks!
use_modular_headers!
pod 'mParticle-Rokt'
pod 'RoktPaymentExtension'
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
# mParticle-Apple-SDK's podspec declares iOS 15.0 but re-exports RoktContracts, which requires 15.6.
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.6'
end
end
end
PODFILE
- name: Build example iOS app
working-directory: example
run: |
flutter pub get
flutter build ios --config-only --no-codesign --release
cd ios
xcodebuild -workspace Runner.xcworkspace -scheme Runner -configuration Release -sdk iphoneos CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO -quiet
xcodebuild -workspace Runner.xcworkspace -scheme Runner -configuration Release -sdk iphoneos -derivedDataPath ../build/dd CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO -quiet
- name: Check the SDK is linked once
run: .github/scripts/check-single-copy.sh example/build/dd/Build/Products/Release-iphoneos/Runner.app
- name: Cleanup
if: ${{ always() }}
working-directory: example
Expand All @@ -54,35 +106,40 @@
name: Build iOS flutter app (Swift Package Manager)
runs-on: macos-15
steps:
# The default checkout folder, mparticle-flutter-sdk, differs from the Dart package name on
# purpose: the example must build from a normal clone (https://github.com/flutter/flutter/issues/186881).
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

Check warning on line 111 in .github/workflows/build-ios.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

artipacked

build-ios.yml:111: credential persistence through GitHub Actions artifacts: does not set persist-credentials: false
with:
# The example app depends on the plugin by path, and Swift Package
# Manager derives a path dependency's identity from the enclosing
# git directory name. That name must equal the Dart package name or
# resolution fails. See https://github.com/flutter/flutter/issues/186881.
path: mparticle_flutter_sdk
- name: Set up Xcode
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
with:
xcode-version: "16.4"
- name: Setup Flutter SDK
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
with:
# Flutter's SwiftPM plugin scheme depends on the generated
# FlutterFramework package, which only exists in 3.44 and later.
# The plugin's Package.swift depends on the generated FlutterFramework
# package, which only exists in Flutter 3.41 and later.
flutter-version: ${{ vars.SPM_FLUTTER_VERSION || '3.47.4' }}
channel: stable
cache: true
- name: Enable Swift Package Manager
run: flutter config --enable-swift-package-manager
- name: Install example dependencies
working-directory: mparticle_flutter_sdk/example
working-directory: example
run: flutter pub get
- name: Remove the CocoaPods spec
# Without the podspec the plugin cannot fall back to CocoaPods unnoticed.
run: rm ios/mparticle_flutter_sdk.podspec
- name: Generate iOS project with Swift Package Manager
working-directory: mparticle_flutter_sdk/example
run: flutter build ios --config-only --no-codesign --release
working-directory: example
run: |
set -o pipefail
flutter build ios --config-only --no-codesign --release | tee config.log
if grep -q 'Running pod install' config.log; then
echo "CocoaPods ran in the Swift Package Manager build"
exit 1
fi
- name: Verify plugin is resolved via Swift Package Manager
working-directory: mparticle_flutter_sdk/example
working-directory: example
run: |
set -euo pipefail
python3 - <<'PY'
Expand Down Expand Up @@ -111,12 +168,14 @@
print("Swift Package Manager is enabled and mparticle_flutter_sdk is in FlutterGeneratedPluginSwiftPackage")
PY
- name: Build example iOS app
working-directory: mparticle_flutter_sdk/example/ios
working-directory: example/ios
run: |
xcodebuild -workspace Runner.xcworkspace -scheme Runner -configuration Release -sdk iphoneos CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO -quiet
xcodebuild -workspace Runner.xcworkspace -scheme Runner -configuration Release -sdk iphoneos -derivedDataPath ../build/dd CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO -quiet
- name: Check the SDK is linked once
run: .github/scripts/check-single-copy.sh example/build/dd/Build/Products/Release-iphoneos/Runner.app
- name: Cleanup
if: ${{ always() }}
working-directory: mparticle_flutter_sdk/example
working-directory: example
run: |
rm -rf build
if command -v flutter &>/dev/null; then
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- iOS: log a warning at launch when the mParticle SDK is linked more than once, for example from both CocoaPods and Swift Package Manager.
- Document Swift Package Manager setup for iOS, including how to add kits, and add a CocoaPods to Swift Package Manager migration guide.

### Changed

- iOS: Rokt methods now return an error when the Rokt kit is not in the app, matching Android. Previously they reported success and did nothing.
- iOS: the CocoaPods spec reads the mParticle Apple SDK version from `Package.swift` (now `>= 9.3.1, < 10.0`), and the Swift package uses the same `mparticle-apple-sdk` package URL as the mParticle kits.
- The example app uses Swift Package Manager on iOS.

## [2.2.1] - 2026-09-15

### Fixed
Expand Down
14 changes: 14 additions & 0 deletions MIGRATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ This document describes upgrade steps for breaking changes in the mParticle Flut

For changes in the underlying native iOS SDK (database migration, deprecated `UIApplicationDelegate` methods, removed `AppDelegateProxy`, regional routing / ATS, Rokt event class renames at the Swift/Objective-C level, etc.), refer to the [mParticle Apple SDK 9 migration guide](https://github.com/mParticle/mparticle-apple-sdk/blob/main/MIGRATING.md#migrating-from-versions--900).

## iOS: moving from CocoaPods to Swift Package Manager

CocoaPods trunk becomes read-only on 2 December 2026, and Flutter uses Swift Package Manager by default from 3.44. The plugin supports both. With Swift Package Manager on, the plugin brings in the mParticle Apple SDK as a Swift package, so any mParticle or Rokt pod left in `ios/Podfile` links a second copy of the SDK. That app builds, but kits register with the wrong copy.

1. Use Flutter 3.44 or later, or Flutter 3.41 or later with `flutter config --enable-swift-package-manager`.
2. Remove every mParticle and Rokt pod from `ios/Podfile`, for example `mParticle-Apple-SDK`, `mParticle-Rokt`, `RoktPaymentExtension` and `RoktSDKPlus`.
- If nothing else is left in the Podfile, run `pod deintegrate` in `ios/`, delete `Podfile` and `Podfile.lock`, and remove the `Pods-Runner` includes from `ios/Flutter/Debug.xcconfig` and `ios/Flutter/Release.xcconfig`.
- Otherwise, run `pod install`.
3. Add the kits as Swift packages to the Runner target, as described in the iOS section of the [README](README.md#iOS).
4. Run a Debug build once and check the console for `[mParticle] The mParticle SDK is loaded more than once`. If it appears, a pod still brings in the SDK.
5. Commit the Xcode project change and `ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved`.

To stay on CocoaPods for now, turn Swift Package Manager off for your app by adding `enable-swift-package-manager: false` under `flutter: config:` in your app's `pubspec.yaml`. Flutter plans to remove this option in a future release.

## Migrating from versions < 2.0.0

Version 2.0.0 wraps the mParticle Apple SDK 9 on iOS. No Dart source changes are required for existing `selectPlacements`, `purchaseFinalized`, or `MPRoktEvents` integrations, but the iOS build configuration must be updated.
Expand Down
51 changes: 42 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,50 @@ To install mParticle on an iOS platform:

[1]: https://app.mparticle.com/apps

2. Install the SDK using CocoaPods:
2. Install the native SDKs. The plugin brings in the mParticle Apple SDK (9.3.1 or later). Add the kits your app uses, such as the Rokt kit for Rokt APIs and `RoktLayout`, with the dependency manager Flutter uses for your app.

```bash
$ # Update your Podfile to depend on 'mParticle-Apple-SDK' version 9.3.1 or later
$ pod install
#### Swift Package Manager

Flutter uses Swift Package Manager by default from 3.44, and you can turn it on from 3.41. The plugin's Swift package brings in the mParticle Apple SDK, so don't add any mParticle or Rokt pods to `ios/Podfile`.

Add each kit to the Runner target in Xcode: open `ios/Runner.xcworkspace`, choose **File > Add Package Dependencies…**, enter the package URL, and add the product to the **Runner** target.

| Kit | Package URL | Product |
| --- | --- | --- |
| Rokt (required for Rokt APIs and `RoktLayout`) | `https://github.com/mparticle-integrations/mp-apple-integration-rokt` | `mParticle-Rokt` |
| Rokt payment extension (shoppable ads payments) | `https://github.com/ROKT/rokt-payment-extension-ios` | `RoktPaymentExtension` |

Use a 9.x kit version. Other kits are listed in the [mParticle Apple SDK README](https://github.com/mParticle/mparticle-apple-sdk#currently-supported-kits); use the Swift package of their 9.x version. If you use `RoktSDKPlus`, it already includes the Rokt kit and the payment extension, so don't add those separately. Commit the Xcode project change and `ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved`.

> **Warning:** Get each mParticle and Rokt SDK from only one dependency manager. If a pod and a Swift package both bring in the SDK, the app still builds, but it contains two copies and kits register with the wrong one. The plugin logs `[mParticle] The mParticle SDK is loaded more than once` at launch when this happens. See [Moving iOS from CocoaPods to Swift Package Manager](MIGRATING.md#ios-moving-from-cocoapods-to-swift-package-manager).

Flutter versions before 3.41 can't build the plugin with Swift Package Manager. Keep it turned off there and use CocoaPods.

#### CocoaPods (deprecated)

CocoaPods trunk becomes read-only on 2 December 2026, so no new mParticle or Rokt pod versions can be published after that date. If Swift Package Manager is turned off for your app, add the kits to `ios/Podfile`:

```ruby
platform :ios, '15.6'

target 'Runner' do
use_frameworks!

# Required for Rokt APIs and RoktLayout.
pod 'mParticle-Rokt'

flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
```

Then run `pod install`.

#### Troubleshooting

- `Rokt is unavailable. Add the mParticle Rokt kit (mParticle-Rokt) to your iOS app.`: the Rokt kit isn't linked into the app. Add it as shown above.
- `[mParticle] The mParticle SDK is loaded more than once`: the SDK comes from both CocoaPods and Swift Package Manager. Remove the mParticle and Rokt pods from `ios/Podfile`.
- Package resolution fails, for example `no versions of '…' match the requirement`, or the build uses an old minimum iOS version: run `flutter clean`, then in Xcode choose **File > Packages > Reset Package Caches**.

The mParticle SDK is initialized by calling the `startWithOptions` method within the `application:didFinishLaunchingWithOptions:` delegate call. Preferably the location of the initialization method call should be one of the last statements in the `application:didFinishLaunchingWithOptions:`. The `startWithOptions` method requires an options argument containing your key and secret and an initial Identity request.

> Note that you must initialize the SDK in the `application:didFinishLaunchingWithOptions:` method. Other parts of the SDK rely on the `UIApplicationDidBecomeActiveNotification` notification to function properly. Failing to start the SDK as indicated will impair it. Also, please do **not** use _GCD_'s `dispatch_async` to start the SDK.
Expand Down Expand Up @@ -332,11 +369,7 @@ await mpInstance?.rokt.selectShoppableAds(
);
```

To enable iOS payment flows for shoppable ads, add the payment extension pod in your app `ios/Podfile` and register it natively after `MParticle.sharedInstance().start(with:)`:

```ruby
pod 'RoktPaymentExtension'
```
To enable iOS payment flows for shoppable ads, add the Rokt payment extension to your app (with Swift Package Manager, the `RoktPaymentExtension` product from `https://github.com/ROKT/rokt-payment-extension-ios`; with CocoaPods, `pod 'RoktPaymentExtension'`) and register it natively after `MParticle.sharedInstance().start(with:)`:

```swift
import RoktPaymentExtension
Expand Down
4 changes: 4 additions & 0 deletions example/ios/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ Runner/GeneratedPluginRegistrant.*
!default.mode2v3
!default.pbxuser
!default.perspectivev3

# Swift Package Manager state. Versions float within the ranges in Runner.xcodeproj so CI builds what new apps get.
**/xcshareddata/swiftpm/
Runner.xcodeproj/project.xcworkspace/
1 change: 0 additions & 1 deletion example/ios/Flutter/Debug.xcconfig
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
1 change: 0 additions & 1 deletion example/ios/Flutter/Release.xcconfig
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
49 changes: 0 additions & 49 deletions example/ios/Podfile

This file was deleted.

Loading
Loading