navhost_typed 0.1.0 copy "navhost_typed: ^0.1.0" to clipboard
navhost_typed: ^0.1.0 copied to clipboard

Type-safe destination objects and navigation helpers for navhost.

navhost_typed #

Type-safe destination objects for navhost.

navhost_typed keeps navhost lightweight while removing stringly-typed navigation from app code. Define destinations as small Dart objects, navigate with those objects, and keep URL parsing only at the deep-link boundary.

Why #

With plain navhost, navigation usually starts as strings:

context.navController.navigate('/posts/42?from=feed');

That is simple, but large apps quickly accumulate duplicated path builders, query keys, and route parsing. navhost_typed keeps the same navhost runtime and adds a thin typed layer:

context.navigateTyped(PostDestination(postId: '42', from: 'feed'));

Core idea #

A destination is a navigation intent. Declare template, override pathParams and queryParams — the concrete location is computed automatically. Add a result type T when the destination can return a value.

// No result
class PostDestination extends TypedDestination {
  static const pathTemplate = '/posts/:postId';

  final String postId;
  final String? from;

  const PostDestination({required this.postId, this.from});

  @override String get template => pathTemplate;
  @override Map<String, String> get pathParams => {'postId': postId};
  @override Map<String, String?> get queryParams => {'from': from};

  @override
  Widget build(BuildContext context) => PostPage(postId: postId, from: from);

  static PostDestination fromRoute(
    Map<String, String> params,
    Map<String, String> query,
  ) =>
      PostDestination(postId: params['postId'] ?? '', from: query['from']);
}

// With result — T is inferred at call sites
class ConfirmDestination extends TypedDestination<bool> {
  const ConfirmDestination({required this.title});
  final String title;

  @override String get template => '/confirm';
  @override Map<String, String?> get queryParams => {'title': title};

  @override Widget build(BuildContext context) => ConfirmDialog(title: title);

  static ConfirmDestination fromRoute(_, Map<String, String> q) =>
      ConfirmDestination(title: q['title'] ?? 'Confirm?');
}

Register destinations as normal navhost routes:

final navController = NavController(
  routes: [
    typedRoute(PostDestination.pathTemplate, PostDestination.fromRoute),
    typedRoute(ConfirmDestination.pathTemplate, ConfirmDestination.fromRoute),
  ],
);

Installation #

dependencies:
  navhost: ^latest
  navhost_typed: ^latest
import 'package:navhost/navhost.dart';
import 'package:navhost_typed/navhost_typed.dart';

API #

TypedDestination<T> #

abstract class TypedDestination<T extends Object?> {
  const TypedDestination();

  String get template;
  Map<String, String> get pathParams => const {};
  Map<String, String?> get queryParams => const {};

  // Derived from template + pathParams + queryParams. Override only for
  // non-standard URLs (fragments, custom encoding, etc.).
  String get location => buildLocation(pathParams, query: queryParams);

  Widget build(BuildContext context);
}
Getter Purpose Default
template Route pattern, e.g. /posts/:postId — (abstract)
pathParams Values for each :param segment {}
queryParams Query string entries; null values are omitted {}
location Concrete URL used for navigation derived
T Result type returned when this destination is popped Object?

Omit T for destinations that do not return a result.

buildLocation

TypedDestination exposes a buildLocation helper used internally to derive location. It can also be called when overriding location with custom logic:

@override
String get location => buildLocation({'id': id}, query: {'ref': ref}) + '#top';

TypedNavInterceptor #

A NavInterceptor subclass that works with typed destinations. Override interceptTyped and return a TypedDestination to redirect, or null to allow the navigation.

class AuthInterceptor extends TypedNavInterceptor {
  @override
  TypedDestination? interceptTyped(String from, String to) {
    if (!isAuthenticated && Uri.parse(to).path.startsWith('/profile')) {
      return AuthDestination(next: to);   // no .location needed
    }
    return null;
  }
}

typedRoute #

typedRoute(ProfileDestination.pathTemplate, ProfileDestination.fromRoute)

Converts incoming navhost path/query params into the typed destination and delegates page creation to destination.build(context).

Typed navigation helpers #

On NavController

// T inferred from destination's result type
navController.navigateTyped(const HomeDestination(), launchSingleTop: true);
navController.navigateTyped(
  ProfileDestination('mathias'),
  popUpTo: const AuthDestination(),
  popUpToInclusive: true,
);

navController.switchTyped(const HomeDestination());

// Future<int?> — T inferred from TypedDestination<int>
final count = await navController.showBottomSheetTyped(CommentsDestination(postId));

// Future<bool?> — T inferred from TypedDestination<bool>
final confirmed = await navController.showDialogTyped(const ConfirmDestination(title: 'Delete?'));

navController.pushTyped(SomeDestination());
navController.pushBottomSheetTyped(SomeDestination());
navController.pushDialogTyped(SomeDestination());

On BuildContext

context.navigateTyped(ProfileDestination('mathias'));

All helpers call regular navhost APIs under the hood, so interceptors, popUpTo, launchSingleTop, results, bottom sheets, dialogs, and nested NavHosts keep working.

  • Put each destination near its page, or in a ui/destinations folder when routes are shared widely.
  • Prefer constructor fields for internal navigation.
  • Keep parsing in fromRoute; that is the only URL boundary.
  • Declare extends TypedDestination<T> only when the destination actually returns a result.
  • Keep rich objects inside constructors for internal navigation, but encode only stable IDs in location for deep links.

Example #

See example/lib/main.dart.

The example includes:

  • path parameters
  • query parameters
  • typed auth redirect via TypedNavInterceptor
  • launchSingleTop
  • typed popUpTo
  • bottom sheets with inferred result type
  • dialogs with inferred result type
  • awaiting typed results

License #

MIT

0
likes
160
points
33
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Type-safe destination objects and navigation helpers for navhost.

Repository (GitHub)
View/report issues

Topics

#navigation #router #typed-routes #navhost

License

MIT (license)

Dependencies

flutter, navhost

More

Packages that depend on navhost_typed