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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ Learn how to send events to Mindbox. Create a new Operation class object and set

Mindbox SDK helps handle push notifications. Configuration and usage instructions can be found in the SDK documentation [here](https://developers.mindbox.ru/docs/firebase-send-push-notifications-flutter), [here](https://developers.mindbox.ru/docs/huawei-send-push-notifications-flutter) and [here](https://developers.mindbox.ru/docs/ios-send-push-notifications-flutter).

### iOS UISceneDelegate migration

If your iOS app declares `UIApplicationSceneManifest` in `Info.plist`
(Flutter's [recommended iOS lifecycle][flutter-uiscene] since 3.41), follow
[UISCENE_MIGRATION.md](UISCENE_MIGRATION.md) to update your `AppDelegate`
and add a `SceneDelegate`. Apps that keep the legacy `AppDelegate`-only
flow don't need any code changes.

[flutter-uiscene]: https://docs.flutter.dev/release/breaking-changes/uiscenedelegate

## Troubleshooting

Refer to the [Example of integration(IOS)](https://github.com/mindbox-cloud/flutter-sdk/tree/develop/mindbox_ios/example) or [Example of integration(Android)](https://github.com/mindbox-cloud/flutter-sdk/tree/develop/mindbox_android/example) in case of any issues.
Expand Down
110 changes: 110 additions & 0 deletions UISCENE_MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Migrating a Mindbox Flutter SDK integration to UISceneDelegate

Starting with Flutter 3.35 the iOS engine ships `FlutterSceneDelegate`, and
since Flutter 3.41 `UISceneDelegate`-based lifecycle is the recommended path
for new iOS Flutter apps. The official Flutter migration guide is the
authoritative source for everything not Mindbox-specific:

- [Flutter UISceneDelegate migration guide][flutter-uiscene]

This document only describes the Mindbox-side glue you need on top of the
Flutter migration: where to forward `.launchScene` and `.universalLink`
events, and what stays in your `AppDelegate`. It does **not** repeat the
Flutter-side steps already covered in the link above (`Info.plist` scene
manifest, `FlutterImplicitEngineDelegate` boilerplate, etc.) — follow them
there and use this page for the Mindbox-specific bits.

If your `Info.plist` does **not** contain `UIApplicationSceneManifest`,
nothing changes for you. The Mindbox iOS SDK pod itself does not depend on
any scene-specific Flutter API, so it keeps building and running on every
Flutter version we supported before. You can update to the latest Mindbox
SDK without touching your code.

## Why this matters for Mindbox integrators

Under `UIApplicationSceneManifest` two `AppDelegate` callbacks Mindbox
previously relied on stop working as before:

- `application(_:didFinishLaunchingWithOptions:)` still fires, but
`launchOptions` is `nil`, so `Mindbox.shared.track(.launch(launchOptions))`
tracks nothing useful.
- `application(_:continue:restorationHandler:)` is never invoked — universal
links arrive in `scene(_:continue:)` instead.

To keep Mindbox receiving launch and universal-link events you need to
forward them from your scene delegate.

## Prerequisites

- **Flutter ≥ 3.41** — required for `FlutterImplicitEngineDelegate` on the
app side (see the Flutter guide above).
- iOS deployment target ≥ 13.0.

## What to add in your scene delegate

Copy
[`example/flutter_example/ios/Runner/SceneDelegate.swift`](https://github.com/mindbox-cloud/flutter-sdk/blob/develop/example/flutter_example/ios/Runner/SceneDelegate.swift)
into your `Runner` target. The two relevant calls:

```swift
override func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
Mindbox.shared.track(.launchScene(connectionOptions))
super.scene(scene, willConnectTo: session, options: connectionOptions)
}

override func scene(
_ scene: UIScene,
continue userActivity: NSUserActivity
) {
Mindbox.shared.track(.universalLink(userActivity))
super.scene(scene, continue: userActivity)
}
```

That replaces, respectively, `Mindbox.shared.track(.launch(launchOptions))`
in `application(_:didFinishLaunchingWithOptions:)` and
`Mindbox.shared.track(.universalLink(userActivity))` in
`application(_:continue:restorationHandler:)`.

You may keep both AppDelegate-side and SceneDelegate-side calls — in scene
mode the AppDelegate-side `.launch(nil)` and `application(_:continue:)` are
inert (`launchOptions == nil`, the method is not invoked), so the two paths
do not produce duplicate events.

## What stays unchanged in your AppDelegate

Even after the scene migration you keep all of the following on your
`AppDelegate` (typically a subclass of `MindboxFlutterAppDelegate`):

- `Mindbox.shared.apnsTokenUpdate(deviceToken:)` in
`application(_:didRegisterForRemoteNotificationsWithDeviceToken:)`.
- `Mindbox.shared.pushClicked(response:)` and
`Mindbox.shared.track(.push(response))` in your
`UNUserNotificationCenterDelegate` methods.
- `Mindbox.shared.registerBGTasks()`.
- Notification permission requests.

`UNUserNotificationCenterDelegate` is a process-global API and is not
affected by scene mode, so push handling needs no changes.

`MindboxFlutterAppDelegate` itself is scene-safe — it does not touch
`window` or `rootViewController` — and continues to work as a base class
unchanged.

## Notification Service / Content Extensions

`MindboxNotificationServiceExtension` and
`MindboxNotificationContentExtension` are extension processes and have
nothing to do with the host app's scene flow. They need no changes.

## Reference implementation

[`example/flutter_example/ios/Runner`](https://github.com/mindbox-cloud/flutter-sdk/tree/develop/example/flutter_example/ios/Runner)
is fully migrated and serves as a working reference for both the
Flutter-side and Mindbox-side parts of the migration.

[flutter-uiscene]: https://docs.flutter.dev/release/breaking-changes/uiscenedelegate
5 changes: 5 additions & 0 deletions example/flutter_example/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/

# IntelliJ related
Expand Down Expand Up @@ -48,3 +50,6 @@ app.*.map.json
# iOS
/ios/Pods/
/ios/Podfile.lock

# FVM Version Cache
.fvm/
2 changes: 0 additions & 2 deletions example/flutter_example/ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,5 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion example/flutter_example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
# platform :ios, '13.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Expand Down
26 changes: 26 additions & 0 deletions example/flutter_example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3A04C4242C18A6EA008FB1C3 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A04C41C2C183779008FB1C3 /* Models.swift */; };
3AFCC3DC2C6A0B4000F047AB /* AppDelegateUsedMindboxDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFCC3DB2C6A0B4000F047AB /* AppDelegateUsedMindboxDelegate.swift */; };
3AFCC3E02C6A0B4000F047AB /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFCC3DF2C6A0B4000F047AB /* SceneDelegate.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
8B81E312DFA9C6FD4EDFB0F6 /* Pods_MindboxNotificationContentExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 201DB479D6282529C705748E /* Pods_MindboxNotificationContentExtension.framework */; };
Expand All @@ -25,6 +26,7 @@
E1B395592BD985350090F3D2 /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B395582BD985350090F3D2 /* NotificationViewController.swift */; };
E1B395602BD985350090F3D2 /* MindboxNotificationContentExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E1B395522BD985350090F3D2 /* MindboxNotificationContentExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
FDACF0FD3A7597BBE1F0C9BD /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E84BF714550B188D12C0B51 /* Pods_Runner.framework */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -86,6 +88,7 @@
3A04C41C2C183779008FB1C3 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = "<group>"; };
3A04C4202C18A4E0008FB1C3 /* Mindbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Mindbox.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3AFCC3DB2C6A0B4000F047AB /* AppDelegateUsedMindboxDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegateUsedMindboxDelegate.swift; sourceTree = "<group>"; };
3AFCC3DF2C6A0B4000F047AB /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
4AC547651623FA3602973E87 /* Pods_MindboxNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MindboxNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4C419D08BE184EB1CC17FC7F /* Pods-MindboxNotificationContentExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MindboxNotificationContentExtension.release.xcconfig"; path = "Target Support Files/Pods-MindboxNotificationContentExtension/Pods-MindboxNotificationContentExtension.release.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -118,6 +121,7 @@
E1B3955D2BD985350090F3D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
E1B395652BD985560090F3D2 /* MindboxNotificationContentExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MindboxNotificationContentExtension.entitlements; sourceTree = "<group>"; };
EE5FB2A6B1C9CC1DF5D97EF8 /* Pods-MindboxNotificationContentExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MindboxNotificationContentExtension.profile.xcconfig"; path = "Target Support Files/Pods-MindboxNotificationContentExtension/Pods-MindboxNotificationContentExtension.profile.xcconfig"; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand All @@ -132,6 +136,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
FDACF0FD3A7597BBE1F0C9BD /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -197,6 +202,7 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
Expand Down Expand Up @@ -234,6 +240,7 @@
isa = PBXGroup;
children = (
3AFCC3DB2C6A0B4000F047AB /* AppDelegateUsedMindboxDelegate.swift */,
3AFCC3DF2C6A0B4000F047AB /* SceneDelegate.swift */,
E19AAF922BD7F53B002D7897 /* Runner.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
Expand Down Expand Up @@ -290,6 +297,9 @@
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
Expand Down Expand Up @@ -355,6 +365,9 @@

/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
);
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
Expand Down Expand Up @@ -583,6 +596,7 @@
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
3AFCC3DC2C6A0B4000F047AB /* AppDelegateUsedMindboxDelegate.swift in Sources */,
3AFCC3E02C6A0B4000F047AB /* SceneDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -1264,6 +1278,18 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
Expand All @@ -26,6 +44,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
Expand Down Expand Up @@ -54,11 +73,13 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
Expand Down
Loading
Loading