Ahmed Fwela

- Improved router delegate

- Rolled back RouterOutlet, and introduced simpler API
... ... @@ -5,18 +5,22 @@ import '../routes/app_pages.dart';
class EnsureAuthMiddleware extends GetMiddleware {
@override
GetNavConfig? redirectDelegate(GetNavConfig route) {
Future<GetNavConfig?> redirectDelegate(GetNavConfig route) async {
// you can do whatever you want here
// but it's preferable to make this method fast
// await Future.delayed(Duration(milliseconds: 500));
if (!AuthService.to.isLoggedInValue) {
final newRoute = Routes.LOGIN_THEN(route.location!);
return GetNavConfig.fromRoute(newRoute);
}
return super.redirectDelegate(route);
return await super.redirectDelegate(route);
}
}
class EnsureNotAuthedMiddleware extends GetMiddleware {
@override
GetNavConfig? redirectDelegate(GetNavConfig route) {
Future<GetNavConfig?> redirectDelegate(GetNavConfig route) async {
if (AuthService.to.isLoggedInValue) {
//NEVER navigate to auth screen, when user is already authed
return null;
... ... @@ -24,6 +28,6 @@ class EnsureNotAuthedMiddleware extends GetMiddleware {
//OR redirect user to another screen
//return GetNavConfig.fromRoute(Routes.PROFILE);
}
return super.redirectDelegate(route);
return await super.redirectDelegate(route);
}
}
... ...
... ... @@ -19,8 +19,10 @@ class HomeView extends GetView<HomeController> {
currentIndex = 1;
}
return Scaffold(
body: GetRouterOutlet(
body: GetRouterOutlet.fromRoute(
initialRoute: Routes.DASHBOARD,
anchorRoute: Routes.HOME,
key: Get.nestedKey(Routes.HOME),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
... ...
import 'package:get/get.dart';
class ProfileController extends GetxController {
//TODO: Implement ProfileController
final count = 0.obs;
@override
void onInit() {
super.onInit();
}
@override
void onReady() {
super.onReady();
}
@override
void onClose() {}
void increment() => count.value++;
}
class ProfileController extends GetxController {}
... ...
... ... @@ -17,8 +17,12 @@ class RootView extends GetView<RootController> {
title: Text(title ?? ''),
centerTitle: true,
),
body: GetRouterOutlet(
body: GetRouterOutlet.fromRoute(
initialRoute: Routes.HOME,
anchorRoute: '/',
filterPages: (afterAnchor) {
return afterAnchor.take(1);
},
),
);
},
... ...
... ... @@ -43,7 +43,6 @@ class AppPages {
binding: LoginBinding(),
),
GetPage(
participatesInRootNavigator: false,
preventDuplicates: true,
name: _Paths.HOME,
page: () => HomeView(),
... ...
... ... @@ -52,56 +52,61 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
final PopMode backButtonPopMode;
final PreventDuplicateHandlingMode preventDuplicateHandlingMode;
GetPage? notFoundRoute;
final GetPage notFoundRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionDelegate<dynamic>? transitionDelegate;
final _resultCompleter = <GetNavConfig, Completer<Object?>>{};
GlobalKey<NavigatorState> get navigatorKey =>
GetNavigation.getxController.key;
GetDelegate({
this.notFoundRoute,
GetPage? notFoundRoute,
this.navigatorObservers,
this.transitionDelegate,
this.backButtonPopMode = PopMode.History,
this.preventDuplicateHandlingMode =
PreventDuplicateHandlingMode.ReorderRoutes,
}) {
}) : notFoundRoute = notFoundRoute ??
GetPage(
name: '/404',
page: () => Scaffold(
body: Text('Route not found'),
),
) {
Get.log('GetDelegate is created !');
}
GetNavConfig? runMiddleware(GetNavConfig config) {
Future<GetNavConfig?> runMiddleware(GetNavConfig config) async {
final middlewares = config.currentTreeBranch.last.middlewares;
if (middlewares == null) {
return config;
}
var iterator = config;
for (var item in middlewares) {
var redirectRes = item.redirectDelegate(iterator);
var redirectRes = await item.redirectDelegate(iterator);
if (redirectRes == null) return null;
iterator = redirectRes;
}
return iterator;
}
void _unsafeHistoryAdd(GetNavConfig config) {
final res = runMiddleware(config);
Future<void> _unsafeHistoryAdd(GetNavConfig config) async {
final res = await runMiddleware(config);
if (res == null) return;
history.add(res);
}
void _unsafeHistoryRemove(GetNavConfig config) {
Future<void> _unsafeHistoryRemove(GetNavConfig config) async {
var index = history.indexOf(config);
if (index >= 0) _unsafeHistoryRemoveAt(index);
if (index >= 0) await _unsafeHistoryRemoveAt(index);
}
GetNavConfig? _unsafeHistoryRemoveAt(int index) {
Future<GetNavConfig?> _unsafeHistoryRemoveAt(int index) async {
if (index == history.length - 1 && history.length > 1) {
//removing WILL update the current route
final toCheck = history[history.length - 2];
final resMiddleware = runMiddleware(toCheck);
final resMiddleware = await runMiddleware(toCheck);
if (resMiddleware == null) return null;
history[history.length - 2] = resMiddleware;
}
... ... @@ -113,38 +118,33 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
// }
/// Adds a new history entry and waits for the result
Future<T?> pushHistory<T>(
Future<void> pushHistory(
GetNavConfig config, {
bool rebuildStack = true,
}) {
}) async {
//this changes the currentConfiguration
final completer = Completer<T?>();
_resultCompleter[config] = completer;
_pushHistory(config);
await _pushHistory(config);
if (rebuildStack) {
refresh();
}
return completer.future;
}
void _removeHistoryEntry(GetNavConfig entry) {
_unsafeHistoryRemove(entry);
final lastCompleter = _resultCompleter.remove(entry);
lastCompleter?.complete(entry);
Future<void> _removeHistoryEntry(GetNavConfig entry) async {
await _unsafeHistoryRemove(entry);
}
void _pushHistory(GetNavConfig config) {
Future<void> _pushHistory(GetNavConfig config) async {
if (config.currentPage!.preventDuplicates) {
final originalEntryIndex =
history.indexWhere((element) => element.location == config.location);
if (originalEntryIndex >= 0) {
switch (preventDuplicateHandlingMode) {
case PreventDuplicateHandlingMode.PopUntilOriginalRoute:
until(config.location!, popMode: PopMode.Page);
await until(config.location!, popMode: PopMode.Page);
break;
case PreventDuplicateHandlingMode.ReorderRoutes:
_unsafeHistoryRemoveAt(originalEntryIndex);
_unsafeHistoryAdd(config);
await _unsafeHistoryRemoveAt(originalEntryIndex);
await _unsafeHistoryAdd(config);
break;
case PreventDuplicateHandlingMode.DoNothing:
default:
... ... @@ -153,7 +153,7 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
return;
}
}
_unsafeHistoryAdd(config);
await _unsafeHistoryAdd(config);
}
// GetPageRoute getPageRoute(RouteSettings? settings) {
... ... @@ -161,33 +161,33 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
// .page();
// }
GetNavConfig? _popHistory() {
Future<GetNavConfig?> _popHistory() async {
if (!_canPopHistory()) return null;
return _doPopHistory();
return await _doPopHistory();
}
GetNavConfig? _doPopHistory() {
return _unsafeHistoryRemoveAt(history.length - 1);
Future<GetNavConfig?> _doPopHistory() async {
return await _unsafeHistoryRemoveAt(history.length - 1);
}
GetNavConfig? _popPage() {
Future<GetNavConfig?> _popPage() async {
if (!_canPopPage()) return null;
return _doPopPage();
return await _doPopPage();
}
GetNavConfig? _pop(PopMode mode) {
Future<GetNavConfig?> _pop(PopMode mode) async {
switch (mode) {
case PopMode.History:
return _popHistory();
return await _popHistory();
case PopMode.Page:
return _popPage();
return await _popPage();
default:
return null;
}
}
// returns the popped page
GetNavConfig? _doPopPage() {
Future<GetNavConfig?> _doPopPage() async {
final currentBranch = currentConfiguration?.currentTreeBranch;
if (currentBranch != null && currentBranch.length > 1) {
//remove last part only
... ... @@ -202,13 +202,13 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
final prevLocation = prevHistoryEntry.location;
if (newLocation == prevLocation) {
//pop the entire history entry
return _popHistory();
return await _popHistory();
}
}
//create a new route with the remaining tree branch
final res = _popHistory();
_pushHistory(
final res = await _popHistory();
await _pushHistory(
GetNavConfig(
currentTreeBranch: remaining.toList(),
location: remaining.last.name,
... ... @@ -218,12 +218,12 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
return res;
} else {
//remove entire entry
return _popHistory();
return await _popHistory();
}
}
Future<GetNavConfig?> popHistory() {
return SynchronousFuture(_popHistory());
Future<GetNavConfig?> popHistory() async {
return await _popHistory();
}
bool _canPopHistory() {
... ... @@ -256,8 +256,7 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
/// gets the visual pages from the current history entry
///
/// visual pages must have the [RouterOutletContainerMiddleWare] middleware
/// with `stayAt` equal to the route name of the visual page
/// visual pages must have [participatesInRootNavigator] set to true
List<GetPage> getVisualPages() {
final currentHistory = currentConfiguration;
if (currentHistory == null) return <GetPage>[];
... ... @@ -278,6 +277,7 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
@override
Widget build(BuildContext context) {
final pages = getVisualPages();
if (pages.length == 0) return SizedBox.shrink();
final extraObservers = navigatorObservers;
return GetNavigator(
key: navigatorKey,
... ... @@ -292,13 +292,13 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
);
}
@override
Future<void> setInitialRoutePath(GetNavConfig configuration) async {
//no need to clear history with Reorder route strategy
// _unsafeHistoryClear();
// _resultCompleter.clear();
await pushHistory(configuration);
}
// @override
// Future<void> setInitialRoutePath(GetNavConfig configuration) async {
// //no need to clear history with Reorder route strategy
// // _unsafeHistoryClear();
// // _resultCompleter.clear();
// await pushHistory(configuration);
// }
@override
Future<void> setNewRoutePath(GetNavConfig configuration) async {
... ... @@ -312,10 +312,10 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
return route;
}
Future<T?> toNamed<T>(String fullRoute) {
Future<void> toNamed(String fullRoute) async {
final decoder = Get.routeTree.matchRoute(fullRoute);
return pushHistory<T>(
return await pushHistory(
GetNavConfig(
currentTreeBranch: decoder.treeBranch,
location: fullRoute,
... ... @@ -324,40 +324,31 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
);
}
Future<T?> offNamed<T>(String fullRoute) async {
Future<void> offNamed(String fullRoute) async {
await popHistory();
return await toNamed(fullRoute);
await toNamed(fullRoute);
}
/// Removes routes according to [PopMode]
/// until it reaches the specifc [fullRoute],
/// DOES NOT remove the [fullRoute]
void until(
Future<void> until(
String fullRoute, {
PopMode popMode = PopMode.Page,
}) {
}) async {
// remove history or page entries until you meet route
var iterator = currentConfiguration;
while (_canPop(popMode) &&
iterator != null &&
iterator.location != fullRoute) {
_pop(popMode);
await _pop(popMode);
// replace iterator
iterator = currentConfiguration;
}
refresh();
}
// GetPage _notFound() {
// return notFoundRoute ??= GetPage(
// name: '/404',
// page: () => Scaffold(
// body: Text('not found'),
// ),
// );
// }
Future<bool> handlePopupRoutes({
Object? result,
}) async {
... ... @@ -380,15 +371,13 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
//Returning false will cause the entire app to be popped.
final wasPopup = await handlePopupRoutes(result: result);
if (wasPopup) return true;
final _popped = _pop(popMode);
final _popped = await _pop(popMode);
refresh();
if (_popped != null) {
//emulate the old pop with result
final lastCompleter = _resultCompleter.remove(_popped);
lastCompleter?.complete(result);
return Future.value(true);
return true;
}
return Future.value(false);
return false;
}
bool _onPopVisualRoute(Route<dynamic> route, dynamic result) {
... ... @@ -419,15 +408,9 @@ class GetNavigator extends Navigator {
List<NavigatorObserver>? observers,
bool reportsRouteUpdateToEngine = false,
TransitionDelegate? transitionDelegate,
// String? name,
}) : super(
//keys should be optional
key: key,
// key != null
// ? key
// : name != null
// ? Get.nestedKey(name)
// : null,
onPopPage: onPopPage ??
(route, result) {
final didPop = route.didPop(result);
... ...
... ... @@ -21,11 +21,11 @@ class RouterOutlet<TDelegate extends RouterDelegate<T>, T extends Object>
RouterOutlet({
TDelegate? delegate,
required List<GetPage> Function(T currentNavStack) pickPages,
required Iterable<GetPage> Function(T currentNavStack) pickPages,
required Widget Function(
BuildContext context,
TDelegate,
List<GetPage>? page,
Iterable<GetPage>? page,
)
pageBuilder,
}) : this.builder(
... ... @@ -76,17 +76,38 @@ class _RouterOutletState<TDelegate extends RouterDelegate<T>, T extends Object>
}
class GetRouterOutlet extends RouterOutlet<GetDelegate, GetNavConfig> {
GetRouterOutlet({
GetRouterOutlet.fromRoute({
required String anchorRoute,
required String initialRoute,
Iterable<GetPage> Function(Iterable<GetPage> afterAnchor)? filterPages,
GlobalKey<NavigatorState>? key,
}) : this(
pickPages: (config) {
var ret = config.currentTreeBranch.pickAfterRoute(anchorRoute);
if (filterPages != null) {
ret = filterPages(ret);
}
return ret;
},
emptyPage: (delegate) =>
Get.routeTree.matchRoute(initialRoute).route ??
delegate.notFoundRoute,
key: key,
);
GetRouterOutlet({
Widget Function(GetDelegate delegate)? emptyWidget,
GetPage Function(GetDelegate delegate)? emptyPage,
required Iterable<GetPage> Function(GetNavConfig currentNavStack) pickPages,
bool Function(Route<dynamic>, dynamic)? onPopPage,
// String? name,
GlobalKey<NavigatorState>? key,
}) : super(
pageBuilder: (context, rDelegate, pages) {
final route = Get.routeTree.matchRoute(initialRoute);
final pageRes = (pages ?? <GetPage<dynamic>?>[route.route])
.whereType<GetPage<dynamic>>()
.toList();
final pageRes = <GetPage?>[
...?pages,
if (pages == null || pages.length == 0)
emptyPage?.call(rDelegate),
].whereType<GetPage>();
if (pageRes.length > 0) {
return GetNavigator(
onPopPage: onPopPage ??
... ... @@ -97,19 +118,13 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, GetNavConfig> {
}
return true;
},
pages: pageRes,
//name: name,
pages: pageRes.toList(),
key: key,
);
}
return (emptyWidget?.call(rDelegate) ?? SizedBox.shrink());
},
pickPages: (currentNavStack) {
final length = Uri.parse(initialRoute).pathSegments.length;
return currentNavStack.currentTreeBranch
.skip(length)
.take(length)
.toList();
},
pickPages: pickPages,
delegate: Get.rootDelegate,
);
... ... @@ -127,12 +142,12 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, GetNavConfig> {
);
}
// extension PagesListExt on List<GetPage> {
// List<GetPage> pickAtRoute(String route) {
// return skipWhile((value) => value.name != route).toList();
// }
extension PagesListExt on List<GetPage> {
Iterable<GetPage> pickAtRoute(String route) {
return skipWhile((value) => value.name != route).toList();
}
// List<GetPage> pickAfterRoute(String route) {
// return skipWhile((value) => value.name != route).skip(1).toList();
// }
// }
Iterable<GetPage> pickAfterRoute(String route) {
return skipWhile((value) => value.name != route).skip(1).toList();
}
}
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import '../../../get.dart';
abstract class _RouteMiddleware {
... ... @@ -49,7 +50,7 @@ abstract class _RouteMiddleware {
/// }
/// ```
/// {@end-tool}
GetNavConfig? redirectDelegate(GetNavConfig route);
Future<GetNavConfig?> redirectDelegate(GetNavConfig route);
/// This function will be called when this Page is called
/// you can use it to change something about the page or give it new page
... ... @@ -118,7 +119,8 @@ class GetMiddleware implements _RouteMiddleware {
void onPageDispose() {}
@override
GetNavConfig? redirectDelegate(GetNavConfig route) => route;
Future<GetNavConfig?> redirectDelegate(GetNavConfig route) =>
SynchronousFuture(route);
}
class MiddlewareRunner {
... ...