Skip to content

Commit c579af0

Browse files
authored
Merge pull request #194 from mindbox-cloud/feature/MOBILE-171-SceneDelegate-support
MOBILE-171: Example `UISceneDelegate` support
2 parents 4783fe4 + 29a8b0a commit c579af0

13 files changed

Lines changed: 429 additions & 64 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ Learn how to send events to Mindbox. Create a new Operation class object and set
3232

3333
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).
3434

35+
### iOS UISceneDelegate migration
36+
37+
If your iOS app declares `UIApplicationSceneManifest` in `Info.plist`
38+
(Flutter's [recommended iOS lifecycle][flutter-uiscene] since 3.41), follow
39+
[UISCENE_MIGRATION.md](UISCENE_MIGRATION.md) to update your `AppDelegate`
40+
and add a `SceneDelegate`. Apps that keep the legacy `AppDelegate`-only
41+
flow don't need any code changes.
42+
43+
[flutter-uiscene]: https://docs.flutter.dev/release/breaking-changes/uiscenedelegate
44+
3545
## Troubleshooting
3646

3747
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.

UISCENE_MIGRATION.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Migrating a Mindbox Flutter SDK integration to UISceneDelegate
2+
3+
Starting with Flutter 3.35 the iOS engine ships `FlutterSceneDelegate`, and
4+
since Flutter 3.41 `UISceneDelegate`-based lifecycle is the recommended path
5+
for new iOS Flutter apps. The official Flutter migration guide is the
6+
authoritative source for everything not Mindbox-specific:
7+
8+
- [Flutter UISceneDelegate migration guide][flutter-uiscene]
9+
10+
This document only describes the Mindbox-side glue you need on top of the
11+
Flutter migration: where to forward `.launchScene` and `.universalLink`
12+
events, and what stays in your `AppDelegate`. It does **not** repeat the
13+
Flutter-side steps already covered in the link above (`Info.plist` scene
14+
manifest, `FlutterImplicitEngineDelegate` boilerplate, etc.) — follow them
15+
there and use this page for the Mindbox-specific bits.
16+
17+
If your `Info.plist` does **not** contain `UIApplicationSceneManifest`,
18+
nothing changes for you. The Mindbox iOS SDK pod itself does not depend on
19+
any scene-specific Flutter API, so it keeps building and running on every
20+
Flutter version we supported before. You can update to the latest Mindbox
21+
SDK without touching your code.
22+
23+
## Why this matters for Mindbox integrators
24+
25+
Under `UIApplicationSceneManifest` two `AppDelegate` callbacks Mindbox
26+
previously relied on stop working as before:
27+
28+
- `application(_:didFinishLaunchingWithOptions:)` still fires, but
29+
`launchOptions` is `nil`, so `Mindbox.shared.track(.launch(launchOptions))`
30+
tracks nothing useful.
31+
- `application(_:continue:restorationHandler:)` is never invoked — universal
32+
links arrive in `scene(_:continue:)` instead.
33+
34+
To keep Mindbox receiving launch and universal-link events you need to
35+
forward them from your scene delegate.
36+
37+
## Prerequisites
38+
39+
- **Flutter ≥ 3.41** — required for `FlutterImplicitEngineDelegate` on the
40+
app side (see the Flutter guide above).
41+
- iOS deployment target ≥ 13.0.
42+
43+
## What to add in your scene delegate
44+
45+
Copy
46+
[`example/flutter_example/ios/Runner/SceneDelegate.swift`](https://github.com/mindbox-cloud/flutter-sdk/blob/develop/example/flutter_example/ios/Runner/SceneDelegate.swift)
47+
into your `Runner` target. The two relevant calls:
48+
49+
```swift
50+
override func scene(
51+
_ scene: UIScene,
52+
willConnectTo session: UISceneSession,
53+
options connectionOptions: UIScene.ConnectionOptions
54+
) {
55+
Mindbox.shared.track(.launchScene(connectionOptions))
56+
super.scene(scene, willConnectTo: session, options: connectionOptions)
57+
}
58+
59+
override func scene(
60+
_ scene: UIScene,
61+
continue userActivity: NSUserActivity
62+
) {
63+
Mindbox.shared.track(.universalLink(userActivity))
64+
super.scene(scene, continue: userActivity)
65+
}
66+
```
67+
68+
That replaces, respectively, `Mindbox.shared.track(.launch(launchOptions))`
69+
in `application(_:didFinishLaunchingWithOptions:)` and
70+
`Mindbox.shared.track(.universalLink(userActivity))` in
71+
`application(_:continue:restorationHandler:)`.
72+
73+
You may keep both AppDelegate-side and SceneDelegate-side calls — in scene
74+
mode the AppDelegate-side `.launch(nil)` and `application(_:continue:)` are
75+
inert (`launchOptions == nil`, the method is not invoked), so the two paths
76+
do not produce duplicate events.
77+
78+
## What stays unchanged in your AppDelegate
79+
80+
Even after the scene migration you keep all of the following on your
81+
`AppDelegate` (typically a subclass of `MindboxFlutterAppDelegate`):
82+
83+
- `Mindbox.shared.apnsTokenUpdate(deviceToken:)` in
84+
`application(_:didRegisterForRemoteNotificationsWithDeviceToken:)`.
85+
- `Mindbox.shared.pushClicked(response:)` and
86+
`Mindbox.shared.track(.push(response))` in your
87+
`UNUserNotificationCenterDelegate` methods.
88+
- `Mindbox.shared.registerBGTasks()`.
89+
- Notification permission requests.
90+
91+
`UNUserNotificationCenterDelegate` is a process-global API and is not
92+
affected by scene mode, so push handling needs no changes.
93+
94+
`MindboxFlutterAppDelegate` itself is scene-safe — it does not touch
95+
`window` or `rootViewController` — and continues to work as a base class
96+
unchanged.
97+
98+
## Notification Service / Content Extensions
99+
100+
`MindboxNotificationServiceExtension` and
101+
`MindboxNotificationContentExtension` are extension processes and have
102+
nothing to do with the host app's scene flow. They need no changes.
103+
104+
## Reference implementation
105+
106+
[`example/flutter_example/ios/Runner`](https://github.com/mindbox-cloud/flutter-sdk/tree/develop/example/flutter_example/ios/Runner)
107+
is fully migrated and serves as a working reference for both the
108+
Flutter-side and Mindbox-side parts of the migration.
109+
110+
[flutter-uiscene]: https://docs.flutter.dev/release/breaking-changes/uiscenedelegate

example/flutter_example/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
*.swp
66
.DS_Store
77
.atom/
8+
.build/
89
.buildlog/
910
.history
1011
.svn/
12+
.swiftpm/
1113
migrate_working_dir/
1214

1315
# IntelliJ related
@@ -48,3 +50,6 @@ app.*.map.json
4850
# iOS
4951
/ios/Pods/
5052
/ios/Podfile.lock
53+
54+
# FVM Version Cache
55+
.fvm/

example/flutter_example/ios/Flutter/AppFrameworkInfo.plist

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,5 @@
2020
<string>????</string>
2121
<key>CFBundleVersion</key>
2222
<string>1.0</string>
23-
<key>MinimumOSVersion</key>
24-
<string>12.0</string>
2523
</dict>
2624
</plist>

example/flutter_example/ios/Podfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Uncomment this line to define a global platform for your project
2-
# platform :ios, '12.0'
2+
# platform :ios, '13.0'
33

44
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
55
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

example/flutter_example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
1313
3A04C4242C18A6EA008FB1C3 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A04C41C2C183779008FB1C3 /* Models.swift */; };
1414
3AFCC3DC2C6A0B4000F047AB /* AppDelegateUsedMindboxDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFCC3DB2C6A0B4000F047AB /* AppDelegateUsedMindboxDelegate.swift */; };
15+
3AFCC3E02C6A0B4000F047AB /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFCC3DF2C6A0B4000F047AB /* SceneDelegate.swift */; };
1516
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
1617
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
1718
8B81E312DFA9C6FD4EDFB0F6 /* Pods_MindboxNotificationContentExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 201DB479D6282529C705748E /* Pods_MindboxNotificationContentExtension.framework */; };
@@ -25,6 +26,7 @@
2526
E1B395592BD985350090F3D2 /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B395582BD985350090F3D2 /* NotificationViewController.swift */; };
2627
E1B395602BD985350090F3D2 /* MindboxNotificationContentExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E1B395522BD985350090F3D2 /* MindboxNotificationContentExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
2728
FDACF0FD3A7597BBE1F0C9BD /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E84BF714550B188D12C0B51 /* Pods_Runner.framework */; };
29+
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
2830
/* End PBXBuildFile section */
2931

3032
/* Begin PBXContainerItemProxy section */
@@ -86,6 +88,7 @@
8688
3A04C41C2C183779008FB1C3 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = "<group>"; };
8789
3A04C4202C18A4E0008FB1C3 /* Mindbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Mindbox.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8890
3AFCC3DB2C6A0B4000F047AB /* AppDelegateUsedMindboxDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegateUsedMindboxDelegate.swift; sourceTree = "<group>"; };
91+
3AFCC3DF2C6A0B4000F047AB /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
8992
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
9093
4AC547651623FA3602973E87 /* Pods_MindboxNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MindboxNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9194
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>"; };
@@ -118,6 +121,7 @@
118121
E1B3955D2BD985350090F3D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
119122
E1B395652BD985560090F3D2 /* MindboxNotificationContentExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MindboxNotificationContentExtension.entitlements; sourceTree = "<group>"; };
120123
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>"; };
124+
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
121125
/* End PBXFileReference section */
122126

123127
/* Begin PBXFrameworksBuildPhase section */
@@ -132,6 +136,7 @@
132136
isa = PBXFrameworksBuildPhase;
133137
buildActionMask = 2147483647;
134138
files = (
139+
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
135140
FDACF0FD3A7597BBE1F0C9BD /* Pods_Runner.framework in Frameworks */,
136141
);
137142
runOnlyForDeploymentPostprocessing = 0;
@@ -197,6 +202,7 @@
197202
9740EEB11CF90186004384FC /* Flutter */ = {
198203
isa = PBXGroup;
199204
children = (
205+
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
200206
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
201207
9740EEB21CF90195004384FC /* Debug.xcconfig */,
202208
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@@ -234,6 +240,7 @@
234240
isa = PBXGroup;
235241
children = (
236242
3AFCC3DB2C6A0B4000F047AB /* AppDelegateUsedMindboxDelegate.swift */,
243+
3AFCC3DF2C6A0B4000F047AB /* SceneDelegate.swift */,
237244
E19AAF922BD7F53B002D7897 /* Runner.entitlements */,
238245
97C146FA1CF9000F007C117D /* Main.storyboard */,
239246
97C146FD1CF9000F007C117D /* Assets.xcassets */,
@@ -290,6 +297,9 @@
290297
productType = "com.apple.product-type.bundle.unit-test";
291298
};
292299
97C146ED1CF9000F007C117D /* Runner */ = {
300+
packageProductDependencies = (
301+
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
302+
);
293303
isa = PBXNativeTarget;
294304
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
295305
buildPhases = (
@@ -355,6 +365,9 @@
355365

356366
/* Begin PBXProject section */
357367
97C146E61CF9000F007C117D /* Project object */ = {
368+
packageReferences = (
369+
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
370+
);
358371
isa = PBXProject;
359372
attributes = {
360373
BuildIndependentTargetsInParallel = YES;
@@ -583,6 +596,7 @@
583596
files = (
584597
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
585598
3AFCC3DC2C6A0B4000F047AB /* AppDelegateUsedMindboxDelegate.swift in Sources */,
599+
3AFCC3E02C6A0B4000F047AB /* SceneDelegate.swift in Sources */,
586600
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
587601
);
588602
runOnlyForDeploymentPostprocessing = 0;
@@ -1264,6 +1278,18 @@
12641278
defaultConfigurationName = Release;
12651279
};
12661280
/* End XCConfigurationList section */
1281+
/* Begin XCLocalSwiftPackageReference section */
1282+
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
1283+
isa = XCLocalSwiftPackageReference;
1284+
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
1285+
};
1286+
/* End XCLocalSwiftPackageReference section */
1287+
/* Begin XCSwiftPackageProductDependency section */
1288+
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
1289+
isa = XCSwiftPackageProductDependency;
1290+
productName = FlutterGeneratedPluginSwiftPackage;
1291+
};
1292+
/* End XCSwiftPackageProductDependency section */
12671293
};
12681294
rootObject = 97C146E61CF9000F007C117D /* Project object */;
12691295
}

example/flutter_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@
55
<BuildAction
66
parallelizeBuildables = "YES"
77
buildImplicitDependencies = "YES">
8+
<PreActions>
9+
<ExecutionAction
10+
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
11+
<ActionContent
12+
title = "Run Prepare Flutter Framework Script"
13+
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
14+
<EnvironmentBuildable>
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
18+
BuildableName = "Runner.app"
19+
BlueprintName = "Runner"
20+
ReferencedContainer = "container:Runner.xcodeproj">
21+
</BuildableReference>
22+
</EnvironmentBuildable>
23+
</ActionContent>
24+
</ExecutionAction>
25+
</PreActions>
826
<BuildActionEntries>
927
<BuildActionEntry
1028
buildForTesting = "YES"
@@ -26,6 +44,7 @@
2644
buildConfiguration = "Debug"
2745
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
2846
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
47+
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
2948
shouldUseLaunchSchemeArgsEnv = "YES">
3049
<MacroExpansion>
3150
<BuildableReference
@@ -54,11 +73,13 @@
5473
buildConfiguration = "Debug"
5574
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
5675
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
76+
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
5777
launchStyle = "0"
5878
useCustomWorkingDirectory = "NO"
5979
ignoresPersistentStateOnLaunch = "NO"
6080
debugDocumentVersioning = "YES"
6181
debugServiceExtension = "internal"
82+
enableGPUValidationMode = "1"
6283
allowLocationSimulation = "YES">
6384
<BuildableProductRunnable
6485
runnableDebuggingMode = "0">

0 commit comments

Comments
 (0)