Flutter Navigator 2.0 - Router does not receive deep link when app is launched from URL?
This is happening because your routeInformationProvider
is overriding that value with an empty route when the app has just been opened instead of receiving the proper route from the operating system.
Changing the emptyrouteInformationProvider
to the one actually used by the Navigator by default.
static PlatformRouteInformationProvider routeInformationProvider =
PlatformRouteInformationProvider(
initialRouteInformation: RouteInformation(
location: PlatformDispatcher.instance.defaultRouteName),
);
Keep in mind that you need to import dart:ui
.
This will use the specific behavior for android and iOS for reading the deep link route when launching the application as it receives the route from the operating system.
This does not fix the iOS problem of the question though.
ikurek
Updated on December 16, 2022Comments
-
ikurek over 1 year
I'm using Flutter Navigator 2.0 with Nested Routers - there's a main router created by calling
MaterialApp.router()
and child routers created asRouter()
widgets with proper RouterDelegates (child routers are used as pages for bottom navigation).In current use case, I want to use a deep link to open one of the pages inside a nested router, so I followed the instructions and configured:
- Android Manifest to receive custom schema and host URL's
-
RouteInformationParser
to parse it into my own model -
PlatformRouteInformationProvider
to notify all nested routers about route change
Everything works fine when application is in foreground (f.e. on splash screen) - I receive a pushRoute event and deep link is handled correctly by the root and nested
Router
widgets. However, when the application is not launched, I do not receive an initialRoute set to my deep link, and I get the default, emptyRouteInformation
instead. Why does that happen? Here are my code samples:Flutter
MainActivity
configuration in Android Manifest:<activity android:name=".MainActivity" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:windowSoftInputMode="adjustResize"> <meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" /> <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/launch_background" /> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <!-- For deeplinking --> <meta-data android:name="flutter_deeplinking_enabled" android:value="true" /> <!-- Accepts links in format myapp://*.myapp.com/ --> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:host="*.myapp.com" android:scheme="myapp" /> </intent-filter> </activity>
Main Application class (used in RunApp):
class MyApp extends StatelessWidget { static PlatformRouteInformationProvider routeInformationProvider = PlatformRouteInformationProvider( initialRouteInformation: const RouteInformation(), ); const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) => FutureBuilder( future: AppInit.initApp(), builder: (context, snapshot) { return _app(); }, ); Widget _app() => MultiRepositoryProvider( providers: AppRepositoryProviders().list, child: MultiBlocProvider( providers: AppBlocProviders().list, child: MaterialApp.router( supportedLocales: LocalizationConfig.supportedLocales, localizationsDelegates: LocalizationConfig.localizationDelegates, theme: LightTheme().themeData, routerDelegate: UserSessionRouter(), routeInformationParser: AppRouteInformationParser(), routeInformationProvider: routeInformationProvider, backButtonDispatcher: RootBackButtonDispatcher(), ), ), ); ... }
RouteInformationParser implementation:
class AppRouteInformationParser extends RouteInformationParser<DeepLinkRoute> { @override Future<DeepLinkRoute> parseRouteInformation( RouteInformation routeInformation) async { if (routeInformation.location.isNullOrEmpty) { return DeepLinkRoute.none(); } else { return DeepLinkParser.parse(routeInformation.location!).fold( (data) => DeepLinkRoute( link: data, route: _getRouteFromDeeplink(data), ), (error) => DeepLinkRoute.none(), ); } } RouteDefinition _getRouteFromDeeplink(DeepLink deepLink) { switch (deepLink.path) { case '/auth/signup': return AppRoutes.authSignup; default: return AppRoutes.none; } } @override RouteInformation restoreRouteInformation(DeepLinkRoute configuration) => RouteInformation( location: configuration.link.path, state: configuration.link.queryParams, ); }
The screen with nested (child) router:
class AuthScreen extends StatefulWidget { const AuthScreen({Key? key}) : super(key: key); @override _AuthScreenState createState() => _AuthScreenState(); } class _AuthScreenState extends State<AuthScreen> { final AuthRouter _routerDelegate = AuthRouter(); ChildBackButtonDispatcher? _backButtonDispatcher; @override void didChangeDependencies() { _initBackButtonDispatcher(); super.didChangeDependencies(); } void _initBackButtonDispatcher() { _backButtonDispatcher ??= ChildBackButtonDispatcher(context.router.backButtonDispatcher!); _backButtonDispatcher?.takePriority(); } @override Widget build(BuildContext context) => MultiBlocProvider( providers: [ BlocProvider( create: (context) => SigninScreenCubit( usersRepository: context.read<UsersRepository>(), )) ], child: Router( routerDelegate: _routerDelegate, backButtonDispatcher: _backButtonDispatcher, routeInformationParser: AppRouteInformationParser(), routeInformationProvider: MyApp.routeInformationProvider, ), ); }
And I test the deep linking with this command:
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "$1"
With given classes, the deep link when launching the app is always empty. My best guess is the instance of
RouteInformationProvider
:static PlatformRouteInformationProvider routeInformationProvider = PlatformRouteInformationProvider(initialRouteInformation: const RouteInformation());
Or some kind of configuration error, but I'm not able to identify it myself.
UPDATE:
I've tested this on iOS, and surprisingly, it works completely reverse than Android - when app is killed, it opens fine with a deep link, but when it's in foreground
Router
never gets update andRouteInformationProvider
andRouteInformationParser
are both never called. I've found some related issues on Flutter GitHub repository, and although they are closed, I don't think they solve my issue. This issue seems to be the almost same as my problem, but I've taken a look at the PR that's supposed to solve it and I can see other users also reporting problems with deep links on iOS. -
ikurek over 2 yearsThank you for the answer! I've tried setting the value of initial route to different things, but I didn't know that Platform can be used to retrieve initial route that way. I've noticed that using your solution with development channel of Flutter seems to fix the deep link handling when the application is in background. It doesn't however solve the iOS issue when the app is in foreground.
-
Cavitedev over 2 yearsI see. I have no hardware to test iOS devices. So I cannot help you there. I am glad this fixed the Android problem though.