'How can a named route have URL parameters in flutter web?

I'm building a web-app which needs to have a route that gets a post ID and then it will fetch the post using the ID.

How can I have URL arguments let's say /post/:id so id is the argument

My app looks like that currently:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // title: "Paste",
      initialRoute: "/",
      theme: ThemeData(
          primarySwatch: Colors.green,
          primaryColor: Colors.blue
      ),
      routes: {
        "/": (context) => HomePage(),
        "/post": (context) => PastieRoute()
      },
      debugShowCheckedModeBanner: false
    );
  }
}

EDIT: This is what I tried according to @BloodLoss and for some reason I don't get anything to the console when accessing localhost:8080/post?id=123

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: "/",
      routes: {
        "/": (context) => HomePage(),
        "/post": (context) => PastieRoute()
      },
      onGenerateRoute: (settings) {
        if (settings.name == "/post") {
          print(settings.arguments); // Doesn't fire :(

          return MaterialPageRoute(
            builder: (context) {
              // TODO
            }
          );
        }
      },
      debugShowCheckedModeBanner: false
    );
  }
}


Solution 1:[1]

tl;dr

//in your example: settings.name = "/post?id=123"
final settingsUri = Uri.parse(settings.name);
//settingsUri.queryParameters is a map of all the query keys and values
final postID = settingsUri.queryParameters['id'];
print(postID); //will print "123"

Drilldown

In a perfect world you would access queryParameters with Uri.base.queryParameters because:

Uri.base

Returns the natural base URI for the current platform. When running in a browser this is the current URL of the current page (from window.location.href). When not running in a browser this is the file URI referencing the current working directory.

But currently there is an issue in flutter where you have #/ in your path which messes the Uri.base interpretation of the Uri.
Follow the issue #33245 until this matter is addressed and you will be able to use Uri.base.queryParameters

Solution 2:[2]

please follow this link further information https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments

on your MaterialApp

  onGenerateRoute: (settings) {
    // If you push the PassArguments route
    if (settings.name == PassArgumentsScreen.routeName) {
      // Cast the arguments to the correct type: ScreenArguments.
      final ScreenArguments args = settings.arguments;

      // Then, extract the required data from the arguments and
      // pass the data to the correct screen.
      return MaterialPageRoute(
        builder: (context) {
          return PassArgumentsScreen(
            title: args.title,
            message: args.message,
          );
        },

or you can nativate like web using this plugin fluro

Solution 3:[3]

This is how I did it. You can edit it as per your requirements. If you want to use ?q= then use the split by or regex accordingly

Here is the example of both passing in argument as well as passing in url as /topic/:id

  Route<dynamic> generateRoute(RouteSettings settings) {
  List<String> pathComponents = settings.name.split('/');
  final Map<String, dynamic> arguments = settings.arguments;
  switch ("/"+pathComponents[1]) {
    case shareTopicView:
      return MaterialPageRoute(
          builder: (context) => TopicPageLayout(topicID: pathComponents[2]));
    case internalTopicView:
      return MaterialPageRoute(
          builder: (context) => TopicPageLayout(topicID: arguments['topicID']));
    default:
      return MaterialPageRoute(builder: (context) => LandingPage());
  }
}

Solution 4:[4]

Add flutter_modular to your flutter web project.

current version: flutter_modular: ^3.1.1

Read dynamic routes section in: https://pub.dev/packages/flutter_modular#dynamic-routes

Example for the URL /post?id=123

  1. Create your main widget with a MaterialApp and call the ´´´MaterialApp().modular()´´´ method.

     //  app_widget.dart
     import 'package:flutter/material.dart';
     import 'package:flutter_modular/flutter_modular.dart';
    
     class AppWidget extends StatelessWidget {
       @override
       Widget build(BuildContext context) {
         return MaterialApp(
           initialRoute: "/",
         ).modular();
       }
     }
    
  2. Create your project module file extending Module:

     // app_module.dart
     class AppModule extends Module {
       @override
       final List<Bind> binds = [];
    
       @override
       final List<ModularRoute> routes = [
           ChildRoute('/', child: (_, __) => HomePage()),
           ChildRoute('/post', child: (_, args) => PostPage(id: args.queryParams['id'])),
      ];
     }
    

3.In main.dart file, wrap the main module in ModularApp to initialize it with Modular:

// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';

import 'app/app_module.dart';

void main() => runApp(ModularApp(module: AppModule(), child: AppWidget()));

Solution 5:[5]

Here's a workaround that uses the 'default' route as my main route.

I did this because it seems to be the only way that Flutter will allow me to open a URL with an ID in it, that doesn't return a 404.

E.g. Flutter does not seem to respect the '?' separator. So a URL with an ID in it, is read by flutter as an unknown URL. E.g. site.com/invoice?id=999 will return a 404, even in /invoice is set up as route.

My goal: I have a 1-page web app that simply displays a single invoice at a time, which corresponds to the ID in the URL.

My URL

app.com/#/xLZppqzSiSxaFu4PB7Ui

The number at the end of the URL is a FireStore Doc ID.

Here's the code in MyApp:

onGenerateRoute: (settings) {
          List<String> pathComponents = settings.name.split('/');
          switch (settings.name) {
            case '/':
              return MaterialPageRoute(
                builder: (context) => Invoice(),
              );
              break;
            default:
              return MaterialPageRoute(
                builder: (context) => Invoice(
                  arguments: pathComponents.last,
                ),
              );
          }
        },

This sends 'xLZppqzSiSxaFu4PB7Ui' to the 'Invoice' widget.

Solution 6:[6]

And here is another way to do it:

My url pattern: www.app.com/#/xLZppqzSiSxaFu4PB7Ui

onGenerateRoute: (settings) {
          List<String> pathComponents = settings.name.split('/');
          if (pathComponents[1] == 'invoice') {
            return MaterialPageRoute(
              builder: (context) {
                return Invoice(arguments: pathComponents.last);
              },
            );
          } else
            return MaterialPageRoute(
              builder: (context) {
                return LandingPage();
              },
            );
          ;
        },

Solution 7:[7]

I'm new to Flutter, and I found a quirky workaround,...

import 'dart:html';

String itemID;

//My url looks like this,... http://localhost:57887/#item_screen/12345
//Counted 13 characters for '#item_screen/' then got the substring as below 

 itemID = window.location.hash.substring(13);
 print(itemID) //12345

Not very sophisticated, but worked :-D

Solution 8:[8]

Try onGenerateRoute with below sample

  final info = settings.arguments as Mediainfo?;

    settings = settings.copyWith(
        name: settings.name! + "?info=" + info!.name, arguments: info);
    return MaterialPageRoute(
        builder: (_) => MediaDetails(info: info), settings: settings);

Solution 9:[9]

This was my solution:

First, kind of seperate, I have an abstract class, AppRoutes, which is just a collection of string-routes, that way they're easily maintainable and switchable.

abstract class AppRoutes {    
  static const String guestGetMember = "/guest_getMember";
  ...

  static render(String url, {Map<String, dynamic>? params}) {
    return Uri(path: url, queryParameters: params ?? {}).toString();
  }
}

Now for the code:

Route<dynamic> generateRoute(RouteSettings settings) {
  Uri uri = Uri.parse(settings.name ?? "");
  Map<String, dynamic> params = {};
  // Convert numeric values to numbers. This is optional.
  // You can instead `int.parse` where needed.
  uri.queryParameters.forEach((key, value) {
    params[key] = int.tryParse(value) ?? value;
  });
  final Map<dynamic, dynamic> arguments = (settings.arguments ?? {}) as Map<dynamic, dynamic>;

  return MaterialPageRoute(builder: (context) {
    switch (uri.path) {
      case AppRoutes.guestGetMember:
        return CardGuestViewProfile(memberID: params['memberID']!);
      case AppRoutes...:
        return...;
      default:
        return AppScreen();
    }

    // Navigator routes update web URLs by default,
    // while `onGeneratedRoute` does not. That last
    // line forces it to. The whole of using url
    // variables for me was so that certainly URLs
    // were easily copiable for sharing.
  }, settings: (RouteSettings(name: settings.name)));
}

And then I call it with

Navigator.pushNamed(context,
  AppRoutes.render(AppRoutes.guestGetMember,
    params: {'memberID': memberID.toString()}),
  arguments: {}));

params will be easily visible to web-users because it's a URL variable, while arguments will not be. This of course doesn't mean that arguments is by any means secure, it just means that non-essential information can be passed through this.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Alex.F
Solution 2
Solution 3 Dheeraj Sarwaiya
Solution 4
Solution 5
Solution 6
Solution 7 ALotLikeEs
Solution 8 rohan kamble
Solution 9