Complete solution example using https://pub.dev/packages/app_links.
Let's assume we have three screens, Home, ProductList, and Product which we use to display many different products.
Main method:
void main() async{
var firstScreen = await DeepLinkParser().getFirstScreen();
runApp( MainApp(firstScreen: firstScreen));
}
We got firstScreen
widget using DeepLinkParser
.
class DeepLinkParser {
DeepLinkParser._();
static final _instance = DeepLinkParser._();
factory DeepLinkParser() => _instance;
final _appLinks = AppLinks();
Future<Uri?> getInitialLink() async {
return _appLinks.getInitialAppLink();
}
Future <Widget> getFirstScreen() async {
Uri? uri = await getInitialLink();
if (uri == null){
return const Home();
}
String fragment = uri.fragment;
if (fragment.contains('/product-list')){
return const ProductList();
}
if (fragment.contains('/product/')){
var lastIndexOfSlash = fragment.lastIndexOf('/');
if (lastIndexOfSlash == fragment.length - 1){
return const ProductList();
}
String id = fragment.substring(lastIndexOfSlash + 1);
return ProductScreen.withId(id: id);
}
return const Home();
}
}
MainApp:
class MainApp extends StatelessWidget {
final Widget firstScreen;
const MainApp({super.key, required this.firstScreen});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
home: firstScreen,
getPages: Routes.routes,
navigatorObservers: [NavigationHistoryObserver()],
debugShowCheckedModeBanner: false,
);
}
}
ProductScreen
has two constructors:
class ProductScreen extends StatelessWidget {
String? id;
ProductScreen.withId({super.key, required this.id});
ProductScreen({super.key});
@override
Widget build(BuildContext context) {
id ??= Get.parameters['id'];
Product product = MockProductService().getById(id!)!;
return Scaffold(
appBar: AppBar(
title: Text('Product ${product.id}'),
centerTitle: true,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//skipped Image and Text widgets with product info
],
),
),
);
}
}
When we call ProductScreen
from ProductList
we use the default constructor (without id parameter) and we pass id
like below:
onTap: () {
Get.toNamed('${Routes.PRODUCT}/${products[index].id}');
}
For that to work, we need to create named routes:
class Routes {
static const HOME = '/';
static const PRODUCT_LIST = '/product-list';
static const PRODUCT = '/product';
static final routes = [
GetPage(
name: HOME,
page: () => const Home(),
transition: Transition.circularReveal,
),
GetPage(
name: PRODUCT_LIST,
page: () => const ProductList(),
transition: Transition.circularReveal,
),
GetPage(
name: '$PRODUCT/:id',
page: () => ProductScreen(),
transition: Transition.circularReveal,
preventDuplicates: false,
),
];
}
It works with one problem: when the app is launched from the deep link
https://example.com/#/product/123
Get pushes three routes into the stack:
/
/product
/product/123
despite I expect only the last one. It creates some problems with back button behavior, which I currently resolve with a lot of if
statements.
UPDATE.
I recently upgraded my projects to GetX 5 (release candidate at the time). GetX 5 always uses Navigator 2.0 internally and supports deep links out of the box. Breaking changes are relatively few. So, I advice anybody interested in the web platform upgrade as well.