55

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
    );
  }
}
1

11 Answers 11

23

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

Sign up to request clarification or add additional context in comments.

3 Comments

"Get.rootDelegate.parameters" gor nav2 or navigation 2.0 with Getx
You can use pub.dev/packages/url_strategy to get rid of the #
I am trying your answer out but myURL/anything just goes to a 404.... only the root myURL works. How do i get to execute the onGenerateROute code?
3

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

5 Comments

This looks more like query arguments type of things, which is not what I would expect from something like /post/:id since id is just a value and not key-value
according to my flutter knowledge, you can't do it like this
Oh alright, I hope it will become a thing for flutter_web.
Can you please check my question edit? and about Fluro. I don't think it's supported for Flutter web? as there's no mentioning about it
@TheOnlyArtz does Fluro support Flutter web? If not any other options available?
3

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

Comments

2

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());
  }
}

Comments

2

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()));

1 Comment

flutter_modular works badly with things like drawers, and swipe back navigation and doesn't actually solve the issue.
2

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.

Comments

1

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);

Comments

1

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();
              },
            );
          ;
        },

Comments

1

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.

Comments

1

This same question I faced while I tried Flutter Web, I tried angular type routing for the path and queries param. It is working perfectly but there is not provided child routing. For the path parsing use path_to_regexp and use URI for the queryParams.

pubspec.yaml

path_to_regexp: ^0.4.0

activated_routes.dart

import 'package:flutter/material.dart';
import 'package:path_to_regexp/path_to_regexp.dart';

typedef RoutingParameters = Route Function(
  RouteSettings settings,
  Map<String, dynamic> param,
);

class _RoutingParametersHandler {
  final String path;
  final RoutingParameters routingParameters;

  _RoutingParametersHandler(this.path, this.routingParameters);
}

class ActivatedRoutes {
  static final ActivatedRoutes _instance = ActivatedRoutes._();
  ActivatedRoutes._();
  static ActivatedRoutes get instance => _instance;

  final List<_RoutingParametersHandler> _routingList = [];

  void addRoute(String path, RoutingParameters routingParameters) {
    _routingList.add(_RoutingParametersHandler(path, routingParameters));
  }

  Route? onGeneratedRoute(RouteSettings settings) {
    Uri uri = Uri.parse(settings.name ?? '');
    _RoutingParametersHandler? routingHandler;
    Map<String, dynamic> param = {};

    for (var element in _routingList) {
      final parameters = <String>[];
      final tokens = parse(element.path, parameters: parameters);
      final regExp = tokensToRegExp(tokens);
      if (regExp.hasMatch(uri.path)) {
        routingHandler = element;
        final match = regExp.matchAsPrefix(uri.path);
        Map<String, String> pathParamMap = extract(parameters, match!);
        pathParamMap.forEach((key, value) {
          param[key] = value;
        });
      }
    }

    if (uri.queryParameters.isNotEmpty) {
      uri.queryParameters.forEach((key, value) {
        param[key] = value;
      });
    }

    if (routingHandler != null) {
      return routingHandler.routingParameters(settings, param);
    }

    return null;
  }
}

routes.dart

import 'package:flutter/material.dart';

class Routes {
  static setUpRoute() {
    ActivatedRoutes.instance.addRoute('/', (settings, params) {
      return MaterialPageRoute(
        settings: settings,
        builder: (context) => const LoginPage(),
      );
    });

    ActivatedRoutes.instance.addRoute('/dashboard/:id', (settings, params) {
      print(params)
      return MaterialPageRoute(
        settings: settings,
        builder: (context) => const DashboardPage(),
      );
    });
  }
}

main.dart

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  useWebConfig();
  Routes.setUpRoute();

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {

    return MaterialApp(
      debugShowCheckedModeBanner: false,
      onGenerateRoute: (settings) =>
          ActivatedRoutes.instance.onGeneratedRoute(settings),
    );
  }
}

routing_ext.dart

import 'package:flutter/material.dart';

extension ContextNavigatorExtension on BuildContext {
  pushNamed(
    String path, {
    Map<String, dynamic>? queryParameters,
    Object? arguments,
  }) {
    Navigator.pushNamed(
      this,
      Uri(
        path: path,
        queryParameters: queryParameters,
      ).toString(),
      arguments: arguments,
    );
  }
}

for the navigation use below code:

context.pushNamed(
    '/dashboard/59',
    queryParameters: {
      'data': 'Hello!',
    },
);

Output:

http://localhost:60031/dashboard/59?data=Hello!

{id: 59, data: Hello!}

Comments

0

You can just use Uri.base to get the full URL and then parse it.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.