Reputation: 604
I'm using Flutter Navigator 2.0 with Nested Routers - there's a main router created by calling MaterialApp.router()
and child routers created as Router()
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:
RouteInformationParser
to parse it into my own modelPlatformRouteInformationProvider
to notify all nested routers about route changeEverything 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, empty RouteInformation
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 and RouteInformationProvider
and RouteInformationParser
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.
Upvotes: 7
Views: 3511
Reputation: 761
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.
Upvotes: 3