This PR forwards scene lifecycle events from the SceneDelegate to
registered plugins.
This PR introduced 3 new public APIs:
* `FlutterSceneLifeCycleDelegate` (protocol) - This is the protocol that
plugins conform to
* `FlutterSceneLifeCycleProvider` (protocol) - This is the protocol that
a SceneDelegate can conform to instead of `FlutterSceneDelegate`
* `FlutterPluginSceneLifeCycleDelegate` (interface) - This is the class
object that a SceneDelegate using `FlutterSceneLifeCycleProvider` can
use to forward scene events to
The flow of events are as followed when SceneDelegate subclasses the
`FlutterSceneDelegate`:
* `FlutterSceneDelegate` receives `sceneDidBecomeActive` event from
UIKit
* `FlutterSceneDelegate` forwards `sceneDidBecomeActive` to
`FlutterPluginSceneLifeCycleDelegate`
* `FlutterPluginSceneLifeCycleDelegate` loops through each
`FlutterEngine` it has and forwards `sceneDidBecomeActive` to the
engine's `FlutterEnginePluginSceneLifeCycleDelegate`
* `FlutterEnginePluginSceneLifeCycleDelegate` loops through each plugin
that conforms to `FlutterSceneLifeCycleDelegate` it has and forwards
`sceneDidBecomeActive` to the plugin
* `FlutterPluginSceneLifeCycleDelegate` forwards `sceneDidBecomeActive`
to `FlutterPluginAppLifeCycleDelegate` using
`sceneDidBecomeActiveFallback`
* `FlutterPluginAppLifeCycleDelegate` loops through all plugins and
calls `applicationDidBecomeActive` if the plugin does not conform to
`FlutterSceneLifeCycleDelegate`
When using `FlutterSceneLifeCycleProvider` instead of
`FlutterSceneDelegate`, scene events are manually forwarded to
`FlutterPluginSceneLifeCycleDelegate` by the iOS developer. For example:
```swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate, FlutterSceneLifeCycleProvider {
var sceneLifeCycleDelegate: FlutterPluginSceneLifeCycleDelegate = FlutterPluginSceneLifeCycleDelegate()
func sceneDidBecomeActive(_ scene: UIScene) {
sceneLifeCycleDelegate.sceneDidBecomeActive(scene)
}
```
Fixes https://github.com/flutter/flutter/issues/174398.
## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
If you need help, consider asking for advice on the #hackers-new channel
on [Discord].
**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.
<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
3.5 KiB
iOS Add-to-App UIScene Integration Test
This directory contains the assets and templates for an integration test that verifies the behavior of Flutter in an add-to-app scenario on iOS, specifically with regards to UIScene lifecycle events.
The test is located at dev/devicelab/bin/tasks/module_uiscene_test_ios.dart.
Purpose
This test ensures that Flutter behaves correctly when integrated into a native iOS application that uses UIScene for lifecycle management. It covers various scenarios, including:
- The native app has migrated to UIScene, but the Flutter plugin has not.
- Both the native app and the Flutter plugin have migrated to UIScene.
- The native app has not migrated to UIScene, but the Flutter plugin has.
- Neither the native app nor the Flutter plugin has migrated to UIScene.
The test checks whether the correct lifecycle events (e.g., scene-based or application-based) are sent to the Flutter plugin in each scenario.
Directory Structure
xcode_uikit_swift: A template for a native iOS app using UIKit and Swift. This app acts as the host for the Flutter module.native: Contains Swift files used to replace files in thexcode_uikit_swifttemplate for different test scenarios. This includes different implementations ofAppDelegate.swift,SceneDelegate.swift,ViewController.swift, and the UI tests that verify the lifecycle events.flutterapp: Contains themain.dartandpubspec.yamlfiles for the Flutter module (my_module) used in the test.flutterplugin: Contains the files for the Flutter plugin (my_plugin) used in the test, including different implementations for migrated and unmigrated lifecycle event handling.
Dart template files don't have a .dart extension so the analyzer will ignore them. They don't work on their own outside of the module, app, or plugin they're copied into.
How to run the test
This test is intended to be run as a Flutter devicelab test. To run it locally, you can use the following command from the flutter/dev/devicelab directory:
../../bin/cache/dart-sdk/bin/dart bin/test_runner.dart test -t module_uiscene_test_ios
You can also run it with a local engine build:
../../bin/cache/dart-sdk/bin/dart bin/test_runner.dart test -t module_uiscene_test_ios --local-engine <your_local_engine> --local-engine-host host_debug
The test create the Flutter module, Flutter plugin, and native iOS app and deletes them at the end of the test. You can also create these files in a specific directory that will not be deleted:
../../bin/cache/dart-sdk/bin/dart bin/test_runner.dart test -t module_uiscene_test_ios --task-args destination=[/path/to/copy/destination]
Adding a new scenario
To add a new test scenario, you need to modify the scenarios map in the Scenarios class in dev/devicelab/bin/tasks/module_uiscene_test_ios.dart.
Each scenario is defined by a list of FileReplacements that swap out files in the generated Xcode project with templates from this directory.
To add a new scenario:
- Add a new entry to the
scenariosmap. The key is the name of your new scenario. - The value is a
List<FileReplacements>. EachFileReplacementsobject takes a source template file and a destination path in the generated project. - If your scenario requires new file content, add the new template files to the appropriate subdirectory (
native,flutterapp, orflutterplugin). - Reference these new template files in the
FileReplacementsfor your new scenario.