diff --git a/app_navigator/lib/src/content_navigator.dart b/app_navigator/lib/src/content_navigator.dart index 3b858fe..21803c8 100644 --- a/app_navigator/lib/src/content_navigator.dart +++ b/app_navigator/lib/src/content_navigator.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:tekartik_app_navigator_flutter/content_navigator.dart'; import 'package:tekartik_app_navigator_flutter/route_aware.dart' as route_aware; import 'package:tekartik_app_navigator_flutter/src/route_aware.dart'; @@ -377,6 +378,11 @@ extension ContentNavigatorBlocExt on ContentNavigatorBloc { /// Push a path. Future pushPath(ContentPath path, {Object? arguments, TransitionDelegate? transitionDelegate}) { + if (kDebugMode) { + if (!path.isValid()) { + throw ArgumentError('Invalid path', path.toString()); + } + } return push(path.routeSettings(arguments), transitionDelegate: transitionDelegate); } diff --git a/app_navigator/lib/src/content_path.dart b/app_navigator/lib/src/content_path.dart index 001598f..63c5458 100644 --- a/app_navigator/lib/src/content_path.dart +++ b/app_navigator/lib/src/content_path.dart @@ -55,6 +55,16 @@ extension ContentPathExt on ContentPath { /// Helper to match a string path directly @Deprecated('Use toPathString instead') String toPath() => toPathString(); + + /// Check if the path is valid and can be pushed + bool isValid() { + for (var field in fields) { + if (!field.isValid()) { + return false; + } + } + return true; + } } /// A field in a content path. diff --git a/app_navigator/lib/src/content_path_field.dart b/app_navigator/lib/src/content_path_field.dart index 501d30b..19beb6d 100644 --- a/app_navigator/lib/src/content_path_field.dart +++ b/app_navigator/lib/src/content_path_field.dart @@ -24,7 +24,9 @@ class ContentPathField { /// Create a field with a name and an optional value ContentPathField(this.name, [String? value]) - : _value = value == _wildcard ? null : value; + : _value = value == _wildcard ? null : value { + assert(name.isNotEmpty, 'name cannot be empty'); + } @override int get hashCode => super.hashCode + (value?.hashCode ?? 0); @@ -75,6 +77,11 @@ class ContentPathField { return '$name/$value'; } } + + /// Check if the field is valid + bool isValid() { + return value != null && value != _wildcard; + } } /// content path list extension diff --git a/app_navigator/lib/src/content_path_part.dart b/app_navigator/lib/src/content_path_part.dart index 1fdf587..f16055b 100644 --- a/app_navigator/lib/src/content_path_part.dart +++ b/app_navigator/lib/src/content_path_part.dart @@ -4,4 +4,8 @@ import 'package:tekartik_app_navigator_flutter/content_navigator.dart'; class ContentPathPart extends ContentPathField { /// Create a part with a name (and no value) ContentPathPart(String name) : super(name, ''); + + /// Always valid + @override + bool isValid() => true; } diff --git a/app_navigator/test/field_test.dart b/app_navigator/test/field_test.dart index 2e7ca88..16afbd5 100644 --- a/app_navigator/test/field_test.dart +++ b/app_navigator/test/field_test.dart @@ -51,5 +51,13 @@ void main() { expect(field.value, 'my_value'); expect(field.name, 'my_name'); }); + test('isValid', () { + var field = ContentPathField('my_name'); + expect(field.isValid(), isFalse); + field.value = 'my_value'; + expect(field.isValid(), isTrue); + var part = ContentPathPart('my_name'); + expect(part.isValid(), isTrue); + }); }); } diff --git a/app_navigator/test/path_test.dart b/app_navigator/test/path_test.dart index 8396042..4fec910 100644 --- a/app_navigator/test/path_test.dart +++ b/app_navigator/test/path_test.dart @@ -197,6 +197,10 @@ void main() { expect(rootContentPath.matchesString('a'), isFalse); expect(rootContentPath.matchesString('/a'), isFalse); }); + test('isValid', () { + expect(SubPath().isValid(), isFalse); + expect((SubPath()..base.value = '123').isValid(), isTrue); + }); }); }