auto_route_library icon indicating copy to clipboard operation
auto_route_library copied to clipboard

[Deep Link] iOS not getting initial route after first launch

Open jdbenito opened this issue 1 year ago • 9 comments

I am working on an app with both deep and universal linking support and we leverage the auto_route plugin.

In Android, opening a deep link when the app is not launched always triggers an initial route with the route specified in the deep link. iOS, unfortunately, does not do the same if it's starting from an unlaunched state -- i.e. the app is always just directed to the default route and not the actual route.

According to the official Flutter documentation:

iOS (not launched)

App gets initialRoute (“/”) and a short time after uses the RouteInformationParser to parse the route and call RouterDelegate.setNewRoutePath, which configures the Navigator with the corresponding Page.

Am I misinterpreting these docs? Is the actual initial route not pushed a short time after at all?

Has anyone else encountered this and what's your workaround?

===== [✓] Flutter (Channel stable, 3.0.5, on macOS 12.1 21C52 darwin-arm, locale en-CA) • Flutter version 3.0.5 at /Users/jdbenito/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision https://github.com/flutter/flutter/commit/f1875d570e39de09040c8f79aa13cc56baab8db1 (4 weeks ago), 2022-07-13 11:24:16 -0700 • Engine revision e85ea0e79c • Dart version 2.17.6 • DevTools version 2.12.2

[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0) • Android SDK at /Users/jdbenito/Library/Android/sdk • Platform android-32, build-tools 31.0.0 • ANDROID_HOME = /Users/jdbenito/Library/Android/sdk • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840) • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 13.4.1) • Xcode at /Applications/Xcode13_4.app/Contents/Developer • CocoaPods version 1.11.3

[✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2021.2) • 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 11.0.12+0-b1504.28-7817840)

[✓] VS Code (version 1.70.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.46.0

jdbenito avatar Aug 08 '22 17:08 jdbenito

@jdbenito How and where are you reading the initial link?

Milad-Akarie avatar Aug 08 '22 17:08 Milad-Akarie

We have an AutoRoute widget that is mapped to the /app/* path. This widget takes care of the parsing of the URL.

In Android, when launching the app via a deep/universal link, I typically see:

  1. the initial route (i.e. /) first get loaded,
  2. followed by the /app/* route getting pushed

In iOS, I don't see #2 happening at all, if starting from an unlaunched state. Everything is fine though if the app is already running.

Here's a snippet of how we built our router:

...
return MaterialApp.router(
              title: ...,
              themeMode: ThemeMode.light,
              theme: AppThemes.lightTheme,
              darkTheme: AppThemes.darkTheme,
              localizationsDelegates: AppLocalizations.localizationsDelegates,
              supportedLocales: AppLocalizations.supportedLocales,
              routeInformationParser: _appRouter.defaultRouteParser(),
              routerDelegate: _appRouter.delegate(
                  navigatorObservers: _analytics.navObserverList),
              builder: ...
            );

In a way, I guess you can say that I'm not even intentionally reading the initial link (neither natively nor in the Flutter side) because at least I expect the plugin to take care of it for me.

jdbenito avatar Aug 08 '22 18:08 jdbenito

@jdbenito are you using the new deep-link flag in IOS?

<key>FlutterDeepLinkingEnabled</key>
<true/>

more info here

Milad-Akarie avatar Aug 09 '22 10:08 Milad-Akarie

Affirmative. That flag is set to TRUE.

jdbenito avatar Aug 09 '22 15:08 jdbenito

@jdbenito I have the exact same problem! However, I don't think it's a problem of this package since my app does not use it.

I'm running the app with flutter 3.0.1 on iOS 15.5. It seems that packages such as app_links work fine as a workaround, but I'm a bit stubborn to just give up, yet.

Have you found any fix, yet? If not, I'd create an issue on the flutter repo and link this issue.

EDIT: I saw you already created an issue that has been closed. So we could revive it.

marvin-kolja avatar Aug 11 '22 08:08 marvin-kolja

Unfortunately not @marvin-kolja . I feel that there's some truth in the speculation that it's a bug within auto_route -- I couldn't spare to do further investigation though due to some time constraints.

As a workaround, I ended up pulling the uni_links plugin. Had to leverage both their initialLink and linkStream APIs and use it conjunction with a route guard in auto_route.

====

(edit)

Looks like you did your own investigation (as per your post in the Flutter board) and I stand corrected.

jdbenito avatar Aug 14 '22 04:08 jdbenito

@jdbenito I'm not quite sure but I think the issue has to do with the scheme you're using Here's a demo using a custom scheme ( works perfectly )

ios_deep_link_demo

Milad-Akarie avatar Aug 15 '22 15:08 Milad-Akarie

@Milad-Akarie did you try it with the app not completely running yet in the background? -- i.e. the app should start solely from the link

jdbenito avatar Aug 19 '22 14:08 jdbenito

@jdbenito Yes, the App was completely killed, not in the background.

Milad-Akarie avatar Sep 08 '22 10:09 Milad-Akarie

I have the same issue. I use app links for deep linking on iOS. On Android deep linking works fine with any state of the application (launched, not launched). In Info.plist I added

        <key>FlutterDeepLinkingEnabled</key>
	<true/>

In runner configuration I added my domain Screenshot 2022-11-02 at 16 30 54

On android I did the same

<!-- App linking -->
            <meta-data android:name="flutter_deeplinking_enabled" android:value="true" />
            <intent-filter android:autoVerify="true" >
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.BROWSABLE" />
                <category android:name="android.intent.category.DEFAULT" />

                <data android:scheme="https" />
                <data android:host="<my domain>" />
            </intent-filter>

On iOS I have an issue with opening the link when the application is completely closed. It opens the login screen and after pushing the home screen. On Android, all works fine.

Here is my routing scheme

routes: [
    AutoRoute(
      path: '/${RouteConstants.main}',
      page: MainScreen,
      initial: true,
      guards: [AuthGuard, QuizGuard],
      children: [
        // Common
        AutoRoute(
          path: RouteConstants.home,
          page: HomeScreen,
          initial: true,
        ),
    ....
    ]
  ),
  AutoRoute(
    path: '/${RouteConstants.login}',
    page: LogInScreen,
    guards: [QuizGuard],
  ),
...
]

Edit: Also I have an async operation in AuthGuard before resolving routes.

Edit 2: I found a temporary solution. I think this problem is related to async operations in the AuthGuard.

  • Application is opening
  • Trying to authenticate for application initial route
  • Deep link is loading
  • Application is trying to open the deep link
  • Authentication completed and opening application initial route
  • Deep link is lost (I don't know why)

I tried to add a completer for the async operation in AuthGuard but now the application opens two home screens. First with the default application initial route and second (above) with a deep link.

Modified AuthGuard

AuthGuard(this._authRepo, this._notificationsRepo);

  bool _isFirstUnauthenticated = true;
  Completer? _authCompleter;

  @override
  Future<void> onNavigation(
    NavigationResolver resolver,
    StackRouter router,
  ) async {
    if (_authCompleter != null) {
      // This fixes ios initial deep link issue when application is closed
      await _authCompleter!.future;
    }
    if (_isFirstUnauthenticated && !_authRepo.isAuthenticated) {
      _authCompleter = Completer();
      // Will be executed on application launch
      _isFirstUnauthenticated = false;
      try {
        await _authRepo.refreshToken();
        await _sendGoogleToken();
      } catch (e) {
        logWarning("Can't authenticate in AuthGuard. Error is $e");
      }
      _authCompleter!.complete();
      _authCompleter = null;
    }
    if (!_authRepo.isAuthenticated) {
      router.pushAndPopUntil(
        LogInRoute(
          onAuthenticated: () {
            resolver.next(true);
            router.removeLast();
          },
        ),
        predicate: (_) => true,
      );
    } else {
      resolver.next(true);
    }
  }

Maybe we should wait for the original deep link inside the main function to work identically on Android and iOS?

InAnadea avatar Nov 02 '22 14:11 InAnadea