Skip to content

· 8 min read · Nadeera Silva

Mobile

Flutter UIScene Migration: Fix iOS 27 Launch Crashes

Flutter apps built with Xcode 27 quit at launch without the UIScene lifecycle. Here's how to migrate a custom AppDelegate, Info.plist and plugins.

An iPhone whose app window splits into three floating glass scene panels, with a cracked screen being sealed by a glowing teal seam, illustrating the UIScene migration and the launch crash it fixes

If your Flutter app opens to a blank screen and then quits as soon as you build it with Xcode 27, the cause is almost always the same: the app hasn't adopted the UIScene lifecycle. This Flutter UIScene migration guide covers what Apple changed, which apps Flutter migrates for you, and the manual steps for apps with a custom AppDelegate, method channels or lifecycle-dependent plugins.

What changed in iOS 27 and Xcode 27

UIKit has had two lifecycles since iOS 13: the older app delegate lifecycle, where AppDelegate owns the window and gets events like applicationDidBecomeActive, and the scene-based lifecycle, where each window lives in a UIWindowScene with its own delegate.

Apple has warned about this for a while. Its UIKit documentation notes that iOS 18.4 started logging "This process does not adopt UIScene lifecycle" and iOS 26 changed it to "UIScene lifecycle will soon be required". From iOS 27, apps built with the latest SDK must adopt the scene-based lifecycle "or they fail to launch".

In practice the failure looks like this in the Xcode console, taken from Flutter's own tracking issue:

Application failed to launch: UIScene life cycle is required for apps built with this SDK.
See Technote TN3187 for more information on migration.

Two details catch people out:

  • It depends on the SDK you build with, not the device. An app built with Xcode 26 keeps working on iOS 27. The same code built with Xcode 27 stops at launch on every device.
  • It isn't Flutter specific. Any UIKit app without a scene manifest is affected. React Native added SceneDelegate support in 0.88 for the same reason.

Flutter's team reproduced it with their own test apps during the Xcode 27 beta (issue #187870, a Flutter 3.44.1 app on iPadOS 27).

Which Flutter apps need manual work

Flutter added the scene APIs in 3.38 and made UIScene the default in 3.41, which also auto-migrates eligible projects (Flutter breaking change: UIScene adoption). Here's where your project probably sits:

Your projectWhat happens with Xcode 27What to do
Flutter 3.41 or later, AppDelegate never editedflutter build ios migrates it and prints "Finished migration to UIScene lifecycle"Build once, review and commit the diff
Flutter 3.41 or later, custom AppDelegateThe CLI warns and skips the migrationFollow the manual steps below
Flutter 3.38 to 3.40APIs exist but there's no auto-migrationUpgrade, or do the manual steps
Older than 3.38The scene APIs don't exist yetUpgrade Flutter first

"Custom AppDelegate" covers more apps than people expect. If you ever added a method channel, a Google Maps API key, a push notification setup, a background task or a deep link handler in AppDelegate.swift, you're in the manual group.

Diagram comparing the Flutter iOS launch sequence before and after UIScene: before, AppDelegate registers plugins and owns the window; after, AppDelegate does process setup, the scene creates the window, then didInitializeImplicitFlutterEngine registers plugins and channels
Simplified launch order with and without UIScene. Plugin registration moves after the scene connects.

The diagram is the key idea. Plugin registration and anything that touches the Flutter view now happen after the scene connects, not inside didFinishLaunchingWithOptions.

Step 1: upgrade Flutter and try the automatic migration

Start on a branch, upgrade, and let the tool try first. At the time of writing, the current stable release is 3.47.7 (8 October 2026).

git checkout -b uiscene-migration
flutter upgrade
flutter --version
flutter clean && flutter pub get
flutter build ios --no-codesign
git diff -- ios/

If the output includes "Finished migration to UIScene lifecycle", review the changes to AppDelegate.swift and Info.plist, run the app, and skip to the testing checklist.

A useful trick when the CLI refuses: generate a fresh reference project with the same Flutter version and diff the iOS runner files against yours.

flutter create --platforms=ios /tmp/uiscene_reference
diff -u /tmp/uiscene_reference/ios/Runner/AppDelegate.swift ios/Runner/AppDelegate.swift
diff -u /tmp/uiscene_reference/ios/Runner/Info.plist ios/Runner/Info.plist

In Flutter 3.47 the template already ships an AppDelegate that conforms to FlutterImplicitEngineDelegate, a SceneDelegate.swift, and a scene manifest in Info.plist.

Step 2: Flutter UIScene migration for a custom AppDelegate

The main change is where plugins and channels get registered. Conform AppDelegate to FlutterImplicitEngineDelegate and move plugin registration, method channels and platform view factories into didInitializeImplicitFlutterEngine.

Here's a typical migrated AppDelegate with one method channel:

import Flutter
import UIKit

@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    // Keep only process-level setup here, such as BGTaskScheduler
    // registration, which must happen before launch finishes.
    // Do not touch window or FlutterViewController here.
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

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

    let deviceChannel = FlutterMethodChannel(
      name: "com.example.app/device",
      binaryMessenger: engineBridge.applicationRegistrar.messenger()
    )
    deviceChannel.setMethodCallHandler { call, result in
      switch call.method {
      case "batteryLevel":
        UIDevice.current.isBatteryMonitoringEnabled = true
        result(Int(UIDevice.current.batteryLevel * 100))
      default:
        result(FlutterMethodNotImplemented)
      }
    }
  }
}

The pattern to remove is the one in most older tutorials:

// Crashes or returns nil after migration: the window doesn't exist yet
let controller = window?.rootViewController as! FlutterViewController

Use engineBridge.applicationRegistrar.messenger() for channels instead. If you really need the view controller itself, the guide suggests subclassing FlutterViewController and doing the work in awakeFromNib, or doing it in the scene delegate.

Step 3: add the scene manifest to Info.plist

Without this entry iOS never creates a scene, so nothing changes. Add it to ios/Runner/Info.plist:

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

Step 4: move UI lifecycle code out of AppDelegate

After migration, UIKit stops calling the UI state methods on the app delegate: applicationDidBecomeActive, applicationWillResignActive, applicationWillEnterForeground, applicationDidEnterBackground and application(_:open:options:).

Create a SceneDelegate that subclasses FlutterSceneDelegate, so Flutter keeps forwarding scene events to the engine and plugins:

import Flutter
import UIKit

class SceneDelegate: FlutterSceneDelegate {
  private var observers: [NSObjectProtocol] = []

  override init() {
    super.init()
    observers.append(
      NotificationCenter.default.addObserver(
        forName: UIScene.didActivateNotification,
        object: nil,
        queue: .main
      ) { _ in
        // Work that used to live in applicationDidBecomeActive,
        // for example clearing the badge or refreshing a token.
      }
    )
  }
}

Then point the manifest at it by changing UISceneDelegateClassName to $(PRODUCT_MODULE_NAME).SceneDelegate, which is what the 3.47 template uses.

Observing UIScene notifications is the least invasive option and leaves Flutter's own forwarding alone. If you need full control and can't subclass FlutterSceneDelegate, the Flutter guide shows how to forward each event with FlutterSceneLifeCycleProvider and FlutterPluginSceneLifeCycleDelegate.

Often the cleaner fix is to move the logic to Dart. App-level "resumed" and "paused" handling usually belongs in an AppLifecycleListener anyway, where it works the same on Android.

Plugins: the part you don't control

Plugins that listen for app lifecycle events, open URLs or user activities need to register for scene events too. Flutter's guide asks plugin authors to adopt FlutterSceneLifeCycleDelegate and call registrar.addSceneDelegate(instance), and to stop relying on UIApplication.shared.delegate.window, which is nil after migration.

For an app, that means:

  1. Run flutter pub outdated and update plugins, especially anything for deep links, auth redirects, notifications, maps and payments.
  2. Read the changelogs for "UIScene" or "scene" entries.
  3. For in-house plugins, follow the plugin section of the Flutter guide. The APIs need Flutter 3.38 or later.

UIScene migration testing checklist

Build with Xcode 27 and test on a real device, not only the simulator:

  • Cold launch from the home screen shows your first screen, not a blank window.
  • Method channels respond (call each one at least once).
  • Deep links and universal links open the right screen from a cold start and from the background.
  • Auth redirects (Sign in with Apple, Google, OAuth web flows) return to the app.
  • Push notifications arrive, and tapping one opens the right screen.
  • Background tasks still register (check the console for BGTaskScheduler errors).
  • Code that used to run in applicationDidBecomeActive still runs when you return to the app.
  • In-app purchases complete and restore. If you sell subscriptions, also check that server-side verification still runs (more on that in my StoreKit subscriptions post).
  • The console no longer prints the "UIScene lifecycle will soon be required" warning.
  • An iPad build works if you support iPad, since scene windows can be resized there.

When I take over an existing Flutter app, a custom AppDelegate is one of the first things I look at. It's where small native changes pile up over the years, and this migration touches all of them. My notes on taking over a live Flutter and PHP app explain how I approach that kind of audit.

Frequently asked questions

Can I turn UIScene off for now?

Flutter documents a temporary switch: prefix the UIApplicationSceneManifest key in Info.plist with an underscore. That only helps while you build with Xcode 26. Apps built with the iOS 27 SDK won't launch without scenes, and from April 2027 Apple requires the iOS 27 SDK for uploads.

Does enable-uiscene-migration: false fix the crash?

No. Setting enable-uiscene-migration: false under flutter: config: in pubspec.yaml hides the CLI migration warning. It doesn't make a non-scene app launch with the iOS 27 SDK.

Does this affect my Android build?

No. UIScene is an Apple platform requirement. Your Android build is unaffected.

My app works on an iOS 27 phone today. Am I safe?

Only until you build with Xcode 27. The requirement is tied to the SDK used for the build, so the next release you build with Xcode 27 is the one that breaks.

Sources

  1. Transitioning to the UIKit scene-based life cycle (Apple)
  2. TN3187: Migrating to the UIKit scene-based life cycle (Apple)
  3. App Store submissions now open for the latest OS releases (Apple, 2026-09-09)
  4. UIScene adoption (breaking change) (Flutter, 2026-09-14)
  5. Flutter SDK archive and release schedule (Flutter)
  6. flutter/flutter#187870, Flutter apps fail to launch on iOS 27 (GitHub, 2026-06-11)
  7. flutter/flutter#187758, guided logs for UIScene crashes on iOS 27 (GitHub, 2026-06-09)
  8. Flutter 3.47.7 iOS app template (AppDelegate, SceneDelegate, Info.plist) (GitHub, 2026-10-08)
  9. React Native 0.88 (React Native, 2026-10-19)
SharePost on XLinkedIn

Comments

Share a question or note about this article.

Loading comments…