simple_routes 1.0.0-beta.1 copy "simple_routes: ^1.0.0-beta.1" to clipboard
simple_routes: ^1.0.0-beta.1 copied to clipboard

Simple, type-safe route and navigation management for go_router.

Simple Routes #

Simple, type-safe route and navigation management for go_router.

Stable release #

We have reached a stable release of Simple Routes with v1.0.0-beta.1! 🎉

This release includes several breaking changes from the 0.x.x versions.
Please see the Migration Guide for more info.

Features #

Simple Routes is a companion package to GoRouter that provides a simple, type-safe way to define your app's routes and navigate between them.

  • Eliminate "magic strings" and the bugs that come with them
  • Simplify route definitions and navigation invocation
  • Enforce type-safe routing requirements
  • Inject and extract path parameters, query parameters, and "extra" route data

Table of Contents #

Getting started #

This package is intended to be used with the GoRouter package.

dependencies:
  go_router: ^12.0.0
  simple_routes: ^1.0.0-beta.1

Usage #

Route definitions #

Basic (simple) routes

Define your routes as classes that extend the SimpleRoute base class.

class ProfileRoute extends SimpleRoute {
  const ProfileRoute();

  @override
  String get path => 'profile';
}

Override the path property with the route's path segment.

If your route is not a child of another route (more on this below), the path will automatically be prefixed with a leading slash, when appropriate.

Route parameters and DataRoutes

For routes that require parameters, extend the DataRoute class.

// Some class or object that you want to pass with your route.
class MyExtraData {
  const MyExtraData(this.someValue);
  final String someValue;
}

// Define your route and/or query parameters as an enum
enum RouteParams {
  userId,
  query,
}

// Define a data class that extends SimpleRouteData
//
// This class should carry all of the data that your routing
// requires, including path parameters, query parameters, and
// "extra" data that you want to pass to your route.
class UserRouteData extends SimpleRouteData {
  const UserRouteData({
    required this.userId,
    required this.extraData,
    this.queryValue,
  });

  // Use a factory constructor to simplify extracting data from 
  // the GoRouterState.
  factory UserRouteData.fromState(GoRouterState state) {
    // Use the extension methods to simplify extracting data from 
    // the GoRouterState by providing the enum value or data type.
    //
    // It is recommended to use these same extensions to validate the 
    // presence of the required data in a `redirect` - more on this in 
    // the GoRouter configuration section below.
    final userId = state.getParam(RouteParams.userId)!;
    final queryValue = state.getQuery(RouteParams.query);
    final extraData = state.getExtra<MyExtraData>()!;

    return UserRouteData(
      userId: userId,
      queryValue: queryValue,
      extraData: extraData,
    );
  }

  // For example, a "user ID" parameter for the path
  // i.e. /user/:userId
  final String userId;

  // Or a query parameter
  final String? queryValue;

  // Or any other data that you want discretely passed to your route.
  final MyExtraData extraData;

  // Override the `parameters` property with a map of your
  // route's path parameters. These will be automatically injected
  // into the route path.
  @override
  Map<Enum, String> get parameters => {
    RouteParams.userId: userId,
  };

  // Override the `query` property with a map of your route's 
  // query parameters. These will be automatically URL encoded
  // and appended to the end of your path.
  //
  // The query map allows null values, so you don't have to worry 
  // about whether or not to include a query parameter.
  @override
  Map<Enum, String?> get query => {
    RouteParams.query: queryValue,
  };

  // Override the `extra` property with any extra data that you 
  // want passed along with your route.
  @override
  MyExtraData get extra => extraData;
}

// Define the route as a DataRoute, typed for your data class.
class UserRoute extends DataRoute<UserRouteData> {
  const UserRoute();

  // Define the route path using the appropriate enum value.
  // Use the `prefixed` property to automatically prefix the
  // enum value name with a colon (e.g. ":userId").
  //
  // To define a path with multiple segments, create an 
  // `Iterable<String>` and use the `toPath` extension method.
  @override
  String get path => ['user', RouteParams.userId.prefixed].toPath();
}

Child routes #

To define a route that is a child of another route, implement the ChildRoute interface, providing the parent route type and overriding the parent property.

class UserDetailsRoute extends DataRoute<UserRouteData> implements ChildRoute<UserRoute> {
  const UserDetailsRoute();

  // Define the route path segment. No need to worry about 
  // leading slashes - they will be added automatically.
  @override
  String get path => 'details';

  // Define the parent route. This will be used to 
  // construct the full path for this route.
  @override
  UserRoute get parent => const UserRoute();
}

Note: Routes that are children of a DataRoute must also be a DataRoute themselves, even if they don't require any data. In cases like these, you can re-use the parent's data class and factory constructor.

However, if they require their own data, the data class must provide it and the data necessary for the parent(s).

GoRouter configuration #

Configuring your GoRoutes is easy. Create an instance of your class and pass in the goPath property to your route's path parameter.

GoRouter(
  // Note that the initialLocation should use the "fullPath" property
  // to include any parent routes, if applicable.
  initialLocation: const HomeRoute().fullPath,
  routes: [
    GoRoute(
      path: const HomeRoute().goPath,
      builder: (context, state) => const HomeScreen(),
    ),
    GoRoute(
      path: const UserRoute().goPath,
      redirect: (context, state) {
        // Use the extension methods to validate that any and all 
        // required values are present.

        if (state.getParam(RouteParams.userId) == null) {
          // If the data is not present, redirect to another route 
          // using the `fullPath` property - this is important, as 
          // the `path` and `goPath` properties only include the 
          // route's segment(s), but not the fully-qualified path.
          return const HomeRoute().fullPath;
        }

        // If all of the data is present, return null to allow the 
        // route to be built.
        return null;
      },
      builder: (context, state) {
        final routeData = UserRouteData.fromState(state);

        return UserScreen(
          userId: routeData.userId,
          query: routeData.queryValue,
          extra: routeData.extraData,
        );
      },
      routes: [
        // Define the child route, using the same data class as
        // the parent route.
        GoRoute(
          path: const UserDetailsRoute().goPath,
          builder: (context, state) {
            final routeData = UserRouteData.fromState(state);

            return UserDetailsScreen(
              userId: routeData.userId,
            );
          },
        ),
      ],
    ),
  ],
);

DataRoute generation

If you need to redirect to a DataRoute, or otherwise need the complete path for a DataRoute, you must use the generate method to generate the full path. The fullPath property will include the template values and will not route properly.

For example, given the following route:

class MyRoute extends DataRoute<MyRouteData> {
  const MyRoute();

  @override
  String get path => ['user', RouteParams.userId.prefixed].toPath();
}

...

// This will not work!
// The return value will be `/user/:userId`
redirect: (context, state) {
  return const MyRoute().fullPath;
}

...

// Instead, use `generate`, like so:
redirect: (context, state) {
  return const MyRoute().generate(MyRouteData(userId: '123'));
}

Once your routes are defined and your router is configured, you can navigate between your routes using the go and push methods.

onPressed: () => const HomeRoute().go(context),

For your routes that require parameters, the go method will enforce that you pass an instance of your data class.

onPressed: () => const UserRoute().go(
  context,
  data: UserRouteData(
    userId: '123',
    queryValue: 'some query value',
    extraData: MyExtraData('some extra data'),
  ),
),

Note: The push method signatures are identical to their corresponding SimpleRoute/DataRoute go methods.

Advanced usage #

Route matching #

Current route

The isCurrentRoute method will determine if your app is at a particular route.

For example, given the following routes:

class BaseRoute extends SimpleRoute {
  const BaseRoute();

  @override
  String get path => 'base';
}

class SubRoute extends SimpleRoute implements ChildRoute<BaseRoute> {
  const SubRoute();

  @override
  String get path => 'sub';

  @override
  BaseRoute get parent => const BaseRoute();
}

and your app is at the location of /base/sub:

// current location: '/base/sub'
if (const SubRoute().isCurrentRoute(context)) {
  debugPrint('We are at SubRoute!');
}

Your app will print We are at SubRoute!.

Parent route

Similar to isCurrentRoute, you can use the isParentRoute method to check whether a route is a parent of the current location.

For example, if your app is at the location of /base/sub:

// current location: '/base/sub'
if (const BaseRoute().isParentRoute(context)) {
  debugPrint('We are at a child of BaseRoute!');
}

Your app will print We are at a child of BaseRoute!.

Note: this method will return false if the current route is an exact match for the route in question (i.e. isCurrentRoute).

For example, if we are at the /base/sub location and use isParentRoute, it will return false:

// current location: '/base/sub'
if (const SubRoute().isParentRoute(context)) {
  debugPrint('We are at a child of SubRoute!');
}

In this case, the print statement will not be executed.

7
likes
0
pub points
43%
popularity

Publisher

verified publisherandyhorn.dev

Simple, type-safe route and navigation management for go_router.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, go_router, mocktail

More

Packages that depend on simple_routes