Skip to content

Commit 7ae082a

Browse files
authored
[quick_actions_ios] UIScene Migration (#11047)
Migrate quick_actions_ios to adopt UIScene Fixes flutter/flutter#170179 ## Pre-Review Checklist **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent eb722e0 commit 7ae082a

7 files changed

Lines changed: 185 additions & 8 deletions

File tree

packages/quick_actions/quick_actions_ios/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 1.2.4
2+
3+
* Adds support for UIScene lifecycle.
4+
* Updates minimum supported SDK version to Flutter 3.38/Dart 3.10.
5+
16
## 1.2.3
27

38
* Updates to Pigeon 26.

packages/quick_actions/quick_actions_ios/example/ios/Runner/AppDelegate.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ import Flutter
66
import UIKit
77

88
@main
9-
@objc class AppDelegate: FlutterAppDelegate {
9+
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
1010
override func application(
1111
_ application: UIApplication,
1212
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
1313
) -> Bool {
14-
GeneratedPluginRegistrant.register(with: self)
1514
super.application(application, didFinishLaunchingWithOptions: launchOptions)
1615
// For UI integration tests. See https://github.com/flutter/plugins/pull/3811.
1716
return false
1817
}
18+
19+
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
20+
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
21+
}
1922
}

packages/quick_actions/quick_actions_ios/example/ios/Runner/Info.plist

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,26 @@
4545
<true/>
4646
<key>UIApplicationSupportsIndirectInputEvents</key>
4747
<true/>
48+
<key>UIApplicationSceneManifest</key>
49+
<dict>
50+
<key>UIApplicationSupportsMultipleScenes</key>
51+
<false/>
52+
<key>UISceneConfigurations</key>
53+
<dict>
54+
<key>UIWindowSceneSessionRoleApplication</key>
55+
<array>
56+
<dict>
57+
<key>UISceneClassName</key>
58+
<string>UIWindowScene</string>
59+
<key>UISceneDelegateClassName</key>
60+
<string>FlutterSceneDelegate</string>
61+
<key>UISceneConfigurationName</key>
62+
<string>flutter</string>
63+
<key>UISceneStoryboardFile</key>
64+
<string>Main</string>
65+
</dict>
66+
</array>
67+
</dict>
68+
</dict>
4869
</dict>
4970
</plist>

packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,4 +223,108 @@ struct QuickActionsPluginTests {
223223
plugin.applicationDidBecomeActive(UIApplication.shared)
224224
}
225225
}
226+
227+
// MARK: - Scene lifecycle tests
228+
229+
@Test func windowScenePerformActionForShortcutItem() async {
230+
let flutterApi: MockFlutterApi = MockFlutterApi()
231+
let mockShortcutItemProvider = MockShortcutItemProvider()
232+
233+
let plugin = QuickActionsPlugin(
234+
flutterApi: flutterApi,
235+
shortcutItemProvider: mockShortcutItemProvider)
236+
237+
let item = UIApplicationShortcutItem(
238+
type: "SearchTheThing",
239+
localizedTitle: "Search the thing",
240+
localizedSubtitle: nil,
241+
icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
242+
userInfo: nil)
243+
244+
await confirmation("shortcut should be handled via windowScene") { confirmed in
245+
flutterApi.launchActionCallback = { aString in
246+
#expect(aString == item.type)
247+
confirmed()
248+
}
249+
250+
let windowScene = UIApplication.shared.connectedScenes.first as! UIWindowScene
251+
var completionSuccess: Bool?
252+
let actionResult = plugin.windowScene(
253+
windowScene,
254+
performActionFor: item
255+
) { success in
256+
completionSuccess = success
257+
}
258+
259+
#expect(actionResult, "windowScene performActionFor must return true.")
260+
#expect(completionSuccess == true)
261+
}
262+
}
263+
264+
@Test func sceneWillConnectToWithoutShortcut() {
265+
let flutterApi: MockFlutterApi = MockFlutterApi()
266+
let mockShortcutItemProvider = MockShortcutItemProvider()
267+
268+
let plugin = QuickActionsPlugin(
269+
flutterApi: flutterApi,
270+
shortcutItemProvider: mockShortcutItemProvider)
271+
272+
let connectResult = plugin.scene(
273+
UIApplication.shared.connectedScenes.first!,
274+
willConnectTo: UIApplication.shared.connectedScenes.first!.session,
275+
options: nil)
276+
#expect(
277+
!connectResult,
278+
"scene willConnectTo must return false if not launched from shortcut.")
279+
}
280+
281+
@Test func sceneDidBecomeActiveLaunchWithoutShortcut() async {
282+
let flutterApi: MockFlutterApi = MockFlutterApi()
283+
let mockShortcutItemProvider = MockShortcutItemProvider()
284+
285+
let plugin = QuickActionsPlugin(
286+
flutterApi: flutterApi,
287+
shortcutItemProvider: mockShortcutItemProvider)
288+
289+
let connectResult = plugin.scene(
290+
UIApplication.shared.connectedScenes.first!,
291+
willConnectTo: UIApplication.shared.connectedScenes.first!.session,
292+
options: nil)
293+
#expect(!connectResult)
294+
295+
await confirmation("launchAction should not be called", expectedCount: 0) { confirmed in
296+
flutterApi.launchActionCallback = { _ in
297+
confirmed()
298+
}
299+
plugin.sceneDidBecomeActive(UIApplication.shared.connectedScenes.first!)
300+
}
301+
}
302+
303+
@Test func sceneDidBecomeActiveLaunchWithShortcut() async {
304+
let item = UIApplicationShortcutItem(
305+
type: "SearchTheThing",
306+
localizedTitle: "Search the thing",
307+
localizedSubtitle: nil,
308+
icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
309+
userInfo: nil)
310+
311+
let flutterApi: MockFlutterApi = MockFlutterApi()
312+
let mockShortcutItemProvider = MockShortcutItemProvider()
313+
314+
let plugin = QuickActionsPlugin(
315+
flutterApi: flutterApi,
316+
shortcutItemProvider: mockShortcutItemProvider)
317+
318+
await confirmation("shortcut should be handled when scene becomes active") { confirmed in
319+
flutterApi.launchActionCallback = { aString in
320+
#expect(aString == item.type)
321+
confirmed()
322+
}
323+
324+
let connectResult = plugin.handleSceneWillConnectTo(shortcutItem: item)
325+
#expect(connectResult, "scene willConnectTo must return true when shortcut is provided.")
326+
327+
plugin.sceneDidBecomeActive(UIApplication.shared.connectedScenes.first!)
328+
}
329+
}
226330
}

packages/quick_actions/quick_actions_ios/example/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ description: Demonstrates how to use the quick_actions plugin.
33
publish_to: none
44

55
environment:
6-
sdk: ^3.9.0
7-
flutter: ">=3.35.0"
6+
sdk: ^3.10.0
7+
flutter: ">=3.38.0"
88

99
dependencies:
1010
flutter:

packages/quick_actions/quick_actions_ios/ios/quick_actions_ios/Sources/quick_actions_ios/QuickActionsPlugin.swift

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@
33
// found in the LICENSE file.
44

55
import Flutter
6+
import UIKit
67

7-
public final class QuickActionsPlugin: NSObject, FlutterPlugin, IOSQuickActionsApi {
8+
public final class QuickActionsPlugin: NSObject, FlutterPlugin, IOSQuickActionsApi,
9+
FlutterSceneLifeCycleDelegate
10+
{
811

912
public static func register(with registrar: FlutterPluginRegistrar) {
1013
let messenger = registrar.messenger()
1114
let flutterApi = IOSQuickActionsFlutterApi(binaryMessenger: messenger)
1215
let instance = QuickActionsPlugin(flutterApi: flutterApi)
1316
IOSQuickActionsApiSetup.setUp(binaryMessenger: messenger, api: instance)
1417
registrar.addApplicationDelegate(instance)
18+
registrar.addSceneDelegate(instance)
1519
}
1620

1721
private let shortcutItemProvider: ShortcutItemProviding
@@ -72,6 +76,46 @@ public final class QuickActionsPlugin: NSObject, FlutterPlugin, IOSQuickActionsA
7276
}
7377
}
7478

79+
// MARK: - FlutterSceneLifeCycleDelegate
80+
81+
public func scene(
82+
_ scene: UIScene,
83+
willConnectTo session: UISceneSession,
84+
options connectionOptions: UIScene.ConnectionOptions?
85+
) -> Bool {
86+
return handleSceneWillConnectTo(shortcutItem: connectionOptions?.shortcutItem)
87+
}
88+
89+
func handleSceneWillConnectTo(shortcutItem: UIApplicationShortcutItem?) -> Bool {
90+
if let shortcutItem {
91+
// Keep hold of the shortcut type and handle it in the
92+
// `sceneDidBecomeActive:` method once the Dart MethodChannel
93+
// is initialized.
94+
launchingShortcutType = shortcutItem.type
95+
return true
96+
}
97+
return false
98+
}
99+
100+
public func sceneDidBecomeActive(_ scene: UIScene) {
101+
if let shortcutType = launchingShortcutType {
102+
handleShortcut(shortcutType)
103+
launchingShortcutType = nil
104+
}
105+
}
106+
107+
public func windowScene(
108+
_ windowScene: UIWindowScene,
109+
performActionFor shortcutItem: UIApplicationShortcutItem,
110+
completionHandler: @escaping (Bool) -> Void
111+
) -> Bool {
112+
handleShortcut(shortcutItem.type)
113+
completionHandler(true)
114+
return true
115+
}
116+
117+
// MARK: - Shortcut handling
118+
75119
func handleShortcut(_ shortcut: String) {
76120
flutterApi.launchAction(action: shortcut) { _ in
77121
// noop

packages/quick_actions/quick_actions_ios/pubspec.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ name: quick_actions_ios
22
description: An implementation for the iOS platform of the Flutter `quick_actions` plugin.
33
repository: https://github.com/flutter/packages/tree/main/packages/quick_actions/quick_actions_ios
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
5-
version: 1.2.3
5+
version: 1.2.4
66

77
environment:
8-
sdk: ^3.9.0
9-
flutter: ">=3.35.0"
8+
sdk: ^3.10.0
9+
flutter: ">=3.38.0"
1010

1111
flutter:
1212
plugin:

0 commit comments

Comments
 (0)