Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## Next version

* Renamed `PathRouteHandler` to `PatternRouteHandler`
* Refactor `PathRouter.of` to not depend on ancestor
It means it is now safe to call `PathRouter.of` at `initState` and other places of a `StatefulWidget`

## [0.1.0-nullsafety.0]

* Migrates to null-safety
Expand Down
2 changes: 1 addition & 1 deletion example/lib/feature_a/route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class FeatureARoute extends NavigationRouteHandler {
bool canOpen(String path) => path == '/feature-a';

@override
Route<T> buildRoute<T>(String path, [RouteArguments? arguments]) {
Route<void> buildRoute(String path, [RouteArguments? arguments]) {
return MaterialPageRoute(
builder: (context) => FeatureAHomePage(
openedBy: arguments?['opened_by'],
Expand Down
2 changes: 1 addition & 1 deletion example/lib/feature_b/route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class FeatureBRoute extends NavigationRouteHandler {
bool canOpen(String path) => path == '/feature-b';

@override
Route<T> buildRoute<T>(String path, [RouteArguments? arguments]) {
Route<void> buildRoute(String path, [RouteArguments? arguments]) {
return MaterialPageRoute(
builder: (context) => FeatureBHomePage(
openedBy: arguments?['opened_by'],
Expand Down
2 changes: 1 addition & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class MyApp extends StatelessWidget {

/// This is the main `Router` instance that will be inserted into the
/// widget tree. It is responsible for registering and opening [RouteHandler]s
/// when a `Router.of(context).open(...)` is called.
/// when a `PathRouter.of(context).open(...)` is called.
class MainAppRouter extends PathRouter with RouteRegistererMixin {
MainAppRouter({required Widget child}) : super(child: child);

Expand Down
4 changes: 2 additions & 2 deletions lib/routing_path.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ library routing_path;

export 'src/errors.dart';
export 'src/navigation_route_handler.dart';
export 'src/path_route_handler.dart';
export 'src/path_router.dart';
export 'src/pattern_route_handler.dart';
export 'src/route_arguments.dart';
export 'src/route_handler.dart';
export 'src/router.dart';
4 changes: 2 additions & 2 deletions lib/src/errors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import 'route_arguments.dart';
/// Exception thrown when attempting to open a route but none registered were
/// capable of opening it.
///
/// See [Router] and [RouteHandler] for more information about how to go about
/// registering and opening routes.
/// See [PathRouter] and [RouteHandler] for more information about how to go
/// about registering and opening routes.
class UnregisteredRouteException implements Exception {
const UnregisteredRouteException(this.route, [this.arguments]) : super();

Expand Down
12 changes: 7 additions & 5 deletions lib/src/navigation_route_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import 'route_handler.dart';
/// Subclasses only need to implement [canOpen] and [buildRoute].
///
/// See [NavigationRouteHandlerMixin] for the core implementations.
abstract class NavigationRouteHandler with NavigationRouteHandlerMixin {
abstract class NavigationRouteHandler<T extends Object?>
with NavigationRouteHandlerMixin<T> {
/// Creates a route handler that presents [Route]
///
/// The [navigatorKey] is the main navigator where this route will attempt
Expand All @@ -31,12 +32,13 @@ abstract class NavigationRouteHandler with NavigationRouteHandlerMixin {
}

/// The [NavigationRouteHandler] core implementation for custom [RouteHandler]s
mixin NavigationRouteHandlerMixin implements RouteHandler {
mixin NavigationRouteHandlerMixin<T extends Object?>
implements RouteHandler<T> {
GlobalKey<NavigatorState>? get navigatorKey => null;

@override
Future<T?> open<T>(String path, [RouteArguments? arguments]) {
final route = buildRoute<T>(path, arguments);
Future<T?> open(String path, [RouteArguments? arguments]) {
final route = buildRoute(path, arguments);
final navigator = navigatorKey ?? NavigationRouteHandler.rootNavigatorKey;
// TODO: allow other presentation methods (replace, pop replace, etc)
final navigatorState = navigator.currentState;
Expand All @@ -54,5 +56,5 @@ mixin NavigationRouteHandlerMixin implements RouteHandler {
}

/// Builds the [Route] which will be pushed on top of the navigator key.
Route<T> buildRoute<T>(String path, [RouteArguments? arguments]);
Route<T> buildRoute(String path, [RouteArguments? arguments]);
}
68 changes: 68 additions & 0 deletions lib/src/path_router.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import 'package:flutter/widgets.dart';

import 'route_handler.dart';

/// The base interface for opening [RouteHandler]s.
///
/// Usually there should be one [PathRouter] instance declared close to the root
/// widget to allow others to call any arbitrary routes from a [BuildContext].
///
/// Typical usage is as follows:
///
/// ```dart
/// PathRouter.of(context).open(...);
/// ```
abstract class PathRouter extends StatelessWidget
implements RouteHandler<dynamic> {
const PathRouter({Key? key, required this.child}) : super(key: key);

/// {@macro flutter.widgets.child}
final Widget child;

@override
Widget build(BuildContext context) =>
_InheritedRouter(router: this, child: child);

/// Attempts to retrieve the closest (inherited) instance of [PathRouter] that
/// encloses the given [context].
///
/// Throws if there is no ancestor [PathRouter] in the [context].
static PathRouter of(BuildContext context) {
final inheritedElement =
context.getElementForInheritedWidgetOfExactType<_InheritedRouter>();
if (inheritedElement == null) {
throw FlutterError(
'PathRouter operation requested with a context that does not include '
'a PathRouter.\n'
'The context used to access the PathRouter must be that of a widget '
'that is a descendant of a PathRouter widget.',
);
}
final inheritedRouter = inheritedElement.widget as _InheritedRouter;
return inheritedRouter.router;
}

/// Attempts to retrieve the closest (inherited) instance of [PathRouter] that
/// encloses the given [context].
///
/// Will return null if there is no ancestor [PathRouter] in the [context]..
static PathRouter? maybeOf(BuildContext context) {
final inheritedElement =
context.getElementForInheritedWidgetOfExactType<_InheritedRouter>();
final inheritedRouter = inheritedElement?.widget as _InheritedRouter?;
return inheritedRouter?.router;
}
}

class _InheritedRouter extends InheritedWidget {
const _InheritedRouter({
Key? key,
required this.router,
required Widget child,
}) : super(key: key, child: child);

final PathRouter router;

@override
bool updateShouldNotify(_InheritedRouter oldWidget) => false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@ import 'utils/path_matcher.dart';
/// See also:
///
/// * [NavigationRouteHandler] the base visual route handler
/// * [PathRouteHandlerMixin] for the core implementation
abstract class PathRouteHandler with PathRouteHandlerMixin {
PathRouteHandler(String path, [String? variablePattern])
/// * [PatternRouteHandlerMixin] for the core implementation
abstract class PatternRouteHandler<T extends Object?>
with PatternRouteHandlerMixin<T> {
PatternRouteHandler(String path, [String? variablePattern])
: pattern = buildPathPattern(path, variablePattern: variablePattern),
super();

@override
final RegExp pattern;
}

/// The [PathRouteHandler] core implementation for [RouteHandler]s.
mixin PathRouteHandlerMixin implements RouteHandler {
/// The [PatternRouteHandler] core implementation for [RouteHandler]s.
mixin PatternRouteHandlerMixin<T extends Object?> implements RouteHandler<T> {
/// The path pattern to be used for matching this route.
///
/// Cannot be null and should have a pattern like
Expand Down
16 changes: 8 additions & 8 deletions lib/src/route_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,30 @@ import 'route_arguments.dart';
/// by calling [open] given the arguments.
///
/// To register a [RouteHandler] into the routing system, it must be added under
/// a [Router]. Due to how the abstraction is laid out, it can also be under
/// a [PathRouter]. Due to how the abstraction is laid out, it can also be under
/// another [RouteHandler] (or [RouteRegistererMixin]).
///
/// To open a [RouteHandler]:
///
/// ```dart
/// Router.of(context).open(path);
/// PathRouter.of(context).open(path);
/// ```
///
/// See also:
///
/// * [NavigationRouteHandler] the base visual route handler
/// * [PathRouteHandler] a patterned path route handler
/// * [PatternRouteHandler] a patterned path route handler
/// * [RouteRegisterer] the base route registerer
/// * [Router] the base interface for opening a route
abstract class RouteHandler {
/// * [PathRouter] the base interface for opening a route
abstract class RouteHandler<T extends Object?> {
/// Returns if this route can be open a given [path].
bool canOpen(String path);

/// Attempts to open this route.
///
/// The [arguments] can be null and will be given to the route if it is able
/// to be opened.
Future<T?> open<T>(String path, [RouteArguments? arguments]);
Future<T?> open(String path, [RouteArguments? arguments]);
}

/// Simple Route aggregator that keeps track of registered routes.
Expand Down Expand Up @@ -60,7 +60,7 @@ class RouteRegisterer with RouteRegistererMixin {
///
/// * [RouteHandler] the base route interface
/// * [RouteRegisterer] a concrete base implementation for this mixin
mixin RouteRegistererMixin implements RouteHandler {
mixin RouteRegistererMixin implements RouteHandler<dynamic> {
/// The currently registered [RouteHandler]s.
///
/// The order where they were registered is the same which [RouteHandler] is
Expand Down Expand Up @@ -101,7 +101,7 @@ mixin RouteRegistererMixin implements RouteHandler {
///
/// if a capable route isn't found throws a [UnregisteredRouteException].
@override
Future<T?> open<T>(String path, [RouteArguments? arguments]) async {
Future<dynamic> open(String path, [RouteArguments? arguments]) async {
final route = routes.firstWhere(
(route) => route.canOpen(path),
orElse: () => throw UnregisteredRouteException(path, arguments),
Expand Down
65 changes: 0 additions & 65 deletions lib/src/router.dart

This file was deleted.

6 changes: 3 additions & 3 deletions test/navigation_route_handler_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import 'package:routing_path/routing_path.dart';

import 'navigation_route_handler_test.mocks.dart';

class _ConcreteRouteHandler extends NavigationRouteHandler {
class _ConcreteRouteHandler<T> extends NavigationRouteHandler<T> {
_ConcreteRouteHandler([GlobalKey<NavigatorState>? navigatorKey])
: super(navigatorKey: navigatorKey);

final MockNavigationRouteHandler mock = MockNavigationRouteHandler();
final MockNavigationRouteHandler<T> mock = MockNavigationRouteHandler();

@override
bool canOpen(String path) => mock.canOpen(path);

@override
Route<T> buildRoute<T>(String path, [RouteArguments? arguments]) =>
Route<T> buildRoute(String path, [RouteArguments? arguments]) =>
mock.buildRoute(path, arguments);
}

Expand Down
8 changes: 4 additions & 4 deletions test/navigation_route_handler_test.mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@ class _FakeRoute<T> extends _i1.Fake implements _i2.Route<T> {}
/// A class which mocks [NavigationRouteHandler].
///
/// See the documentation for Mockito's code generation for more information.
class MockNavigationRouteHandler extends _i1.Mock
implements _i3.NavigationRouteHandler {
class MockNavigationRouteHandler<T extends Object?> extends _i1.Mock
implements _i3.NavigationRouteHandler<T> {
MockNavigationRouteHandler() {
_i1.throwOnMissingStub(this);
}

@override
_i4.Future<T?> open<T>(String? path, [_i5.RouteArguments? arguments]) =>
_i4.Future<T?> open(String? path, [_i5.RouteArguments? arguments]) =>
(super.noSuchMethod(Invocation.method(#open, [path, arguments]),
returnValue: Future.value(null)) as _i4.Future<T?>);
@override
_i2.Route<T> buildRoute<T>(String? path, [_i5.RouteArguments? arguments]) =>
_i2.Route<T> buildRoute(String? path, [_i5.RouteArguments? arguments]) =>
(super.noSuchMethod(Invocation.method(#buildRoute, [path, arguments]),
returnValue: _FakeRoute<T>()) as _i2.Route<T>);
}
Loading