Chuyển đến nội dung chính

The UIScene migration: the iOS change that will crash unmigrated Flutter apps

Most Flutter migrations are optional until they are annoying. This one is different: once Apple enforces the requirement, apps that have not adopted the UIScene lifecycle will crash on startup. Not degrade. Crash.

Apple requires UIKit apps built with the latest SDK to use the UIScene lifecycle starting in the release following iOS 26. Apple has not announced the exact enforcement date. Flutter has supported the migration since 3.38, and 3.47 makes the surrounding platform changes concrete by raising the floors: iOS 13 → 15 and macOS 10.15 → 12, to support Xcode 27.

What UIScene actually changes

The conceptual shift is a split of responsibilities that used to live in one object:

  • AppDelegate now handles process events and overall application lifecycle
  • UISceneDelegate handles UI lifecycle — foreground, background, active, resign

Two consequences follow, and both break code:

  1. Plugin registration moves. Register in didInitializeImplicitFlutterEngine, not application:didFinishLaunchingWithOptions:.
  2. Launch options become nil in application:didFinishLaunchingWithOptions: after migration. They are delivered to scene:willConnectToSession:options: instead.

That second one is the silent killer. Deep links, push notification payloads, and shortcut items that arrived through launch options simply stop arriving, with no compile error.

The good news: it is often automatic

As of Flutter 3.41, if your AppDelegate has not been customised, the Flutter CLI migrates your app automatically when you run flutter run or flutter build ios. A large fraction of apps are already done and did not notice.

You have work to do if you customised AppDelegate, ship an add-to-app integration, or maintain a plugin.

Info.plist

The migration adds an Application Scene Manifest:

<key>UIApplicationSceneManifest</key>
<dict>
  <key>UIApplicationSupportsMultipleScenes</key>
  <false/>
  <key>UISceneConfigurations</key>
  <dict>
    <key>UIWindowSceneSessionRoleApplication</key>
    <array>
      <dict>
        <key>UISceneClassName</key>
        <string>UIWindowScene</string>
        <key>UISceneDelegateClassName</key>
        <string>FlutterSceneDelegate</string>
        <key>UISceneConfigurationName</key>
        <string>flutter</string>
        <key>UISceneStoryboardFile</key>
        <string>Main</string>
      </dict>
    </array>
  </dict>
</dict>

A useful debugging trick: prefix UIApplicationSceneManifest with an underscore to temporarily disable UIScene support, and remove the underscore to re-enable. That gives you a fast A/B when something breaks.

AppDelegate

Move plugin registration out of didFinishLaunchingWithOptions and into the new delegate callback:

@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    // GeneratedPluginRegistrant no longer belongs here
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
    GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)

    let batteryChannel = FlutterMethodChannel(
      name: "samples.flutter.dev/battery",
      binaryMessenger: engineBridge.applicationRegistrar.messenger()
    )
  }
}

Method channels and platform view factories both need the messenger from engineBridge.applicationRegistrar, not the old application-level one.

For add-to-app, add a scene delegate — usually a one-liner:

import UIKit
import Flutter

class SceneDelegate: FlutterSceneDelegate {}

If your host app cannot subclass FlutterSceneDelegate, implement FlutterSceneLifeCycleProvider and forward each scene callback to a FlutterPluginSceneLifeCycleDelegate instance.

If you maintain a plugin

Plugin authors carry the heaviest load, because every app depending on you inherits your migration state.

Bump your constraints, then adopt the protocol and register for scene callbacks:

environment:
  sdk: ^3.10.0
  flutter: ">=3.38.0"
public final class MyPlugin: NSObject, FlutterPlugin, FlutterSceneLifeCycleDelegate {
  public static func register(with registrar: FlutterPluginRegistrar) {
    registrar.addApplicationDelegate(instance)
    registrar.addSceneDelegate(instance)
  }
}

Then map your old callbacks:

AppDelegate methodScene delegate equivalent
applicationDidBecomeActivesceneDidBecomeActive
applicationWillResignActivesceneWillResignActive
applicationWillEnterForegroundsceneWillEnterForeground
applicationDidEnterBackgroundsceneDidEnterBackground
application:openURL:options:scene:openURLContexts:
application:continueUserActivity:scene:continueUserActivity:
application:didFinishLaunchingWithOptions:scene:willConnectToSession:options:

The APIs that stop working

A set of long-standing UIKit singletons are deprecated under the scene model. Each has a scene-scoped replacement:

DeprecatedReplacement
UIScreen.mainUIWindowScene.screen
UIApplication.shared.delegate.windowregistrar.viewController.view.window
UIApplication.shared.keyWindowUIWindowScene.keyWindow (iOS 15+)
UIApplication.shared.windowsUIWindowScene.windows

Note that UIWindowScene.keyWindow requires iOS 15 — which is exactly the floor Flutter 3.47 just raised you to. The two changes are related, not coincidental.

The genuinely hard case: early initialisation

Some Apple APIs must be configured before application:didFinishLaunchingWithOptions: returns — BGTaskScheduler, UNUserNotificationCenterDelegate, HKHealthStore. Under the scene model, plugin registration happens later than that.

There is no way for a plugin to solve this alone. The documented pattern is for the plugin to expose a public method that the app developer calls from their own AppDelegate:

class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    BGTaskPlugin.shared.registerBackgroundHandler(identifier: "com.example.task")
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

If you use background tasks, health data, or notification delegates, check your plugin’s docs for exactly this pattern. It is the migration step most likely to be missed and least likely to be caught by a test.

Your migration checklist

  1. Build on 3.47 and let the automatic migration run if your AppDelegate is stock.
  2. Add the UIApplicationSceneManifest to Info.plist if it is not there.
  3. Move GeneratedPluginRegistrant into didInitializeImplicitFlutterEngine, along with method channels and platform view factories.
  4. Re-test every entry point: deep links, universal links, push notification taps, home-screen quick actions. Launch options are nil now.
  5. Grep for the deprecated singletonsUIScreen.main, keyWindow, UIApplication.shared.windows — in your code and your plugins.
  6. Raise deployment targets to iOS 15 and macOS 12, then run flutter build ios --config-only.
  7. Check plugins that need early init and add the required call in your AppDelegate.
  8. Do not use enable-uiscene-migration: false as anything other than a short-term unblock — it hides the warning, not the eventual crash.

The bottom line

This is the one migration in Flutter 3.47 with a hard failure mode. The mechanical parts are well documented and largely automated, so most apps will pass with a rebuild. The parts that will actually bite are the untested paths: a deep link that no longer carries its payload, a plugin that registers a background task too late, a keyWindow call buried in a dependency. Do the rebuild today, then spend an hour opening your app from every external entry point you support. That hour is much cheaper than a launch-day crash report.


Originally published on FlutterCook. Read the latest version there — that copy is the one kept up to date.

Nhận xét

Bài đăng phổ biến từ blog này

Thiết kế giao diện với DotNetBar (Phần 1)

Đây là phiên bản DotNetBar hỗ trợ C# và Visual Basic https://www.dropbox.com/s/wx80jpvgnlrmtux/DotNetBar.rar  , phiên bản này hỗ trợ giao diện Metro cực kỳ “dễ thương” Các bạn load về và cài đặt, khi cài đặt xong sẽ có source code mẫu của tất cả các control. Để sử dụng được các control của DotNetBar các bạn nhớ add item vào controls box. Thiết kế giao diện với DotNetBar, giao diện sẽ rất đẹp. Link các video hướng dẫn chi tiết cách sử dụng và coding: http://www.devcomponents.com/dotnetbar/movies.aspx Hiện tại DotNetBar có rất nhiều công cụ cực mạnh, trong đó có 3 công cụ dưới đây: DotNetBar for Windows Forms Requires with Visual Studio 2003, 2005, 2008, 2010 or 2012.   DotNetBar for WPF Requires with Visual Studio 2010 or 2012 and Windows Presentation Foundation.   DotNetBar for Silverlight Requires with Visual Studio 2010 or 2012 and Silverlight. Dưới đây là một số hình ảnh về các control trong DotnetBar.   Metro User Interface  controls with Metro Tiles, toolba...

5 concepts every Flutter dev should know

  Phụ lục: State management architecture Testing IDE Shortcuts Platform channel Maintaining a project Tôi đã làm việc với Flagship trong một thời gian dài, và đây là những điều mà tôi phát hiện ra là điều cần phải có đối với bất kỳ nhà phát triển Flagship nào, về tổng thể nó sẽ khiến bạn trở thành một nhà phát triển Flagship giỏi trong thời gian dài. 1. State management architecture Đây là một trong những chủ đề quan trọng nhất trong cộng đồng thiết bị rung, nó khá quan trọng nếu bạn muốn duy trì một dự án rung kích thước trung bình hoặc lớn. Nó sẽ giúp tạo một dự án suôn sẻ và thêm các tính năng mới một cách hoàn hảo.  2. Testing Đây là một chủ đề duy nhất mà tôi không hiểu tại sao nó lại quan trọng trước đó trong sự nghiệp của tôi, nhưng khi tôi tiến lên trong sự nghiệp của mình và có kinh nghiệm với nhiều dự án và vấn đề xảy ra trong môi trường sản xuất. Tôi đã nhận ra một cách khó khăn, tại sao điều này lại quan trọng như vậy. Nếu bạn vẫn muốn có thêm lý do để cân nhắc thử...

Announcing Flutter 2

  Phụ lục: Flutter on the web Flutter 2 on desktops, foldables, and embedded devices The growing Flutter ecosystem Dart: The secret sauce behind Flutter Flutter 2: Available now Hôm nay, chúng tôi sẽ công bố Flutter 2: một bản nâng cấp lớn cho Flutter cho phép các nhà phát triển tạo các ứng dụng đẹp, nhanh chóng và di động cho bất kỳ nền tảng nào. Với Flutter 2, bạn có thể sử dụng cùng một cơ sở mã để gửi các ứng dụng gốc cho năm hệ điều hành: IOS, Android, Windows, macOS và Linux; cũng như trải nghiệm web nhắm mục tiêu các trình duyệt như Chrome, Firefox, Safari hoặc Edge. Flutter thậm chí có thể được nhúng vào ô tô, TV và thiết bị gia dụng thông minh, mang đến trải nghiệm di động và lan tỏa nhất cho thế giới điện toán xung quanh. Mục tiêu của chúng tôi là thay đổi cơ bản cách các nhà phát triển nghĩ về việc xây dựng ứng dụng, bắt đầu không phải với nền tảng bạn đang nhắm mục tiêu mà là với trải nghiệm bạn muốn tạo. Flutter cho phép bạn tạo ra những trải nghiệm tuyệt đẹp trong đó ...