flutterdartdeep-linkingflutter-getx

How can we handle deep links in flutter with Get X for go to Custom pages of the application?


How can we handle deep links in flutter with Get X for go to Custom pages of the application ?

By default, by adding the desired address to the Android Manifest file:

        <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.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="http" android:host="flutterbooksample.com" />
            <data android:scheme="https" android:host="flutterbooksample.com"/>
        </intent-filter>

when the application opens, the main page of application will be displayed to us.

I am looking to implement this I can direct the user to any page of the application that is needed. A practical example is to pay on the web page and return to the application. When we return, we should show the user a message about the status of the payment, not direct the user to the first page of the application.


Solution

  • 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.