Repository navigation
Description
Steps to reproduce
It looks like in 3.32.x there has been a regression with deep links handling with the app closed in iOS. We did some git bisect and it looks to be related to this commit #164247 @hellohuanlin .
Steps:
- Install an app with https deep links managed by Flutter.
- Kill the app.
- Trigger the deep link (for example with xcrun simctl openurl booted )
- App is started but instead of opening the link in the app it opens in Safari.
Our app takes a couple seconds to load and deep links can only be handled after that. So what we do when onGenerateRoute is called and the app didn't load yet we save the link for later handling.
In the example we do a mock of this behavior that can trigger the error. This only seems to happen with https links because they are handled by the phone browser. After some debugging it looks like it has this behavior because we return null in generateRoute when the app didn't load. This didn't happen before the linked commit.
Expected results
Link is handled in app.
Actual results
Link is opened in Safari.
Code sample
import 'dart:async';
import 'package:flutter/material.dart';
// When a deep link is triggered while the app is closed we first need to load the app so we save it here to handle later.
String? globalPendingDeepLink;
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool loading = true;
@override
void initState() {
super.initState();
// Mock initial app loading. Deep links would be handled after the app loads.
Future.delayed(Duration(seconds: 3)).then((_) {
if (!mounted) return;
setState(() {
loading = false;
});
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: createOnGenerateRoute(),
onGenerateInitialRoutes: (route) {
print("FLUTTER: onGenerateInitialRoutes: $route");
return [
MaterialPageRoute(
builder: (context) {
return HomeWidget(appLoading: loading);
},
),
];
},
);
}
RouteFactory createOnGenerateRoute() {
return (RouteSettings settings) {
final pathOrUri = settings.name;
print("FLUTTER: onGenerateRoute: $pathOrUri initial app loading: $loading");
if (loading) {
// Save link until the app is loaded to handle later.
globalPendingDeepLink = pathOrUri;
return null;
}
final route = _handleRouteSettings(pathOrUri);
if (route != null) {
return route;
}
return null;
};
}
}
class HomeWidget extends StatefulWidget {
final bool appLoading;
const HomeWidget({super.key, required this.appLoading});
@override
State<HomeWidget> createState() => HomeWidgetState();
}
class HomeWidgetState extends State<HomeWidget> {
@override
void didUpdateWidget(covariant HomeWidget oldWidget) {
if (widget.appLoading != oldWidget.appLoading && !widget.appLoading) {
print("FLUTTER: app has been loaded. Pending link $globalPendingDeepLink");
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (globalPendingDeepLink != null) {
final route = _handleRouteSettings(globalPendingDeepLink);
if (route != null) {
globalPendingDeepLink = null;
Navigator.of(context, rootNavigator: true).push<void>(route);
}
}
});
}
;
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Home")),
body: Center(child: widget.appLoading ? CircularProgressIndicator() : Text("Home")),
);
}
}
Route<Object?>? _handleRouteSettings(String? pathOrUri) {
print("HANDLE ROUTE SETTINGS: $pathOrUri");
if (pathOrUri == null || pathOrUri == "/") {
return null;
}
return MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(),
body: Center(child: Text("Deep link: $pathOrUri")),
),
);
}
Screenshots or Video
No response
Logs
Since the app needs to be closed when opening through the deep link we don't have logs.
Flutter Doctor output
Doctor output
[✓] Flutter (Channel stable, 3.32.4, on macOS 15.5 24F74 darwin-arm64, locale es-ES) [387ms]
• Flutter version 3.32.4 on channel stable at /Users/jorge/.local/share/mise/installs/flutter/3.32.4-stable
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 6fba2447e9 (hace 3 días), 2025-06-12 19:03:56 -0700
• Engine revision 8cd19e509d
• Dart version 3.8.1
• DevTools version 2.45.1
[✗] Android toolchain - develop for Android devices [368ms]
• Android SDK at /opt/homebrew/Caskroom/android-platform-tools/36.0.0
✗ cmdline-tools component is missing.
Try installing or updating Android Studio.
Alternatively, download the tools from https://developer.android.com/studio#command-line-tools-only and make sure to set the ANDROID_HOME environment variable.
See https://developer.android.com/studio/command-line for more details.
[✓] Xcode - develop for iOS and macOS (Xcode 16.2) [835ms]
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 16C5032a
• CocoaPods version 1.16.2
[✓] Chrome - develop for the web [11ms]
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 2022.3) [10ms]
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)
[✓] VS Code (version 1.101.0) [9ms]
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.112.0
[✓] Connected device (4 available) [6,2s]
• iphone J (wireless) (mobile) • 00008130-00016C4A0E90001C • ios • iOS 18.4.1 22E252
• iPhone 15 Pro (mobile) • 52288C1D-7C4B-4E41-8F79-FC26DEEF0EC3 • ios • com.apple.CoreSimulator.SimRuntime.iOS-18-3 (simulator)
• macOS (desktop) • macos • darwin-arm64 • macOS 15.5 24F74 darwin-arm64
• Chrome (web) • chrome • web-javascript • Google Chrome 137.0.7151.104
! Error: Browsing on the local area network for iPad J. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac.
The device must be opted into Developer Mode to connect wirelessly. (code -27)
[✓] Network resources [287ms]
• All expected network resources are available.
! Doctor found issues in 1 category.
Activity
darshankawar commentedon Jun 17, 2025
@js2702 Thanks for the report. I tried a sample deeplinks app from this issue and ran it in latest stable 3.32.4 with which I didn't observe the reported behavior. Upon running the command
xcrun simctl openurl booted 'sampleiosdeeplinks:///details'after killing the app from recent screen, it opened the app properly.Can you provide us the same sample demo that we can use to verify this further ?
js2702 commentedon Jun 17, 2025
@darshankawar I updated the issue description with a sample. After further testing we found the issue only happens with https links and it's because we return null in the onGenerateRoute, because we handle the link later in the initial app start flow. It does not seem to trigger with custom url schemes. Our theory is because only the app would handle the link, with https also the browser can handle the link.
One thing that we found months ago when doing this deep link handling is that on Android the deep link from a closed app comes through the function
onGenerateInitialRouteswhereas on iOS it comes through onGenerateRoute. Maybe this difference could be related?darshankawar commentedon Jun 18, 2025
I believe this is WAI. Please see #161867.
re: the issue reported, I used the provided code sample by you in same deeplinks sample I used earlier and after killing the app from recent apps screen, ran
xcrun simctl openurl booted 'sampleiosdeeplinks:///details'which took me to the details screen correctly.Did I use the deeplink url correctly ? If not, please provide the link to append to the command, or you may try the same by using the code you provided in the issue I linked earlier and run the above provided command.
js2702 commentedon Jun 19, 2025
@darshankawar I understand if that difference works as intended. I was just trying to find some explanation to the regression from the commit linked in the description.
The other issue you mentioned with an example is not valid. The problem only occurs when handling https urls. And for that there needs to be a registered domain. Maybe the Flutter team has a package id to use that can be used in debug to test https deep links.
The command is the same, just changing the url to a https one.
Our dart code example can be used in conjunction with the https configuration for iOS to associate a domain with the app.
https://docs.flutter.dev/cookbook/navigation/set-up-universal-links#associate-your-app-with-your-web-domain
darshankawar commentedon Jun 20, 2025
Thanks for the update.
I'll try to set this up and verify if I can replicate / validate this or not. Meantime, I'll keep the issue open for team's tracking on behavior reported.
40 remaining items
flutter-triage-bot commentedon Apr 13, 2026
This issue is assigned to @okorohelijah but has had no recent status updates. Please consider unassigning this issue if it is not going to be addressed in the near future. This allows people to have a clearer picture of what work is actually planned. Thanks!
okorohelijah commentedon Apr 21, 2026
Here is my friction log/ observation:
I completely closed the app from recent apps, it opened on safari with the content {'error': 'Not found: /details'} but then there is a notification to open the app at the top of the website which i can confirm opens. I don't think this should be the expected behavior?? @hellohuanlin @vashworth
I tried using the custom url in debug mode cold start but i got error: "in Ios 14 debug mode Flutter apps can only be lunched from flutter tooling, ides with flutter plugins, or form xcode. Alternatively build in profile or release mode"
So it seems like on physical devices, custom shemes and https can only be cold started when in profile or release modes and the cold start works on the simulator in debug mode but https links doesnt seem to work on simulators because of the AASA maybe? and release/profile modes cant be run on simulators so if you want to test an https, it has to be on physical devices?
NB: You also have to make sure you're using the right bundle identifier that has the
Associaited Domains Capabilityokorohelijah commentedon Apr 21, 2026
Analysis by Gemini which seems to make sense:
as seen in
flutter/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm
Lines 467 to 475 in 7428429
flutter/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm
Lines 170 to 176 in 7428429
vashworth commentedon Apr 22, 2026
No that's not the expected behavior. It should open the app directly
vashworth commentedon Apr 22, 2026
@okorohelijah Were you able to recreate the issue with profile/release mode?
okorohelijah commentedon Apr 22, 2026
@vashworth yes, I was able to recreate the issue in profile mode
hellohuanlin commentedon Apr 27, 2026
@okorohelijah does this issue turn out to be a regression from #164247?
okorohelijah commentedon Jun 15, 2026
@js2702 If you want, I add a comment in #185430 (comment) explaining the cause
Remove manual Safari fallback for unhandled universal links (flutter#…
github-actions commentedon Aug 25, 2026
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.