Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/1026 Android back gesture should follow navigation history #1056

Merged
merged 9 commits into from
Oct 30, 2024
32 changes: 32 additions & 0 deletions lib/features/app/navigation/navigation_history_observer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:built_collection/built_collection.dart';
import 'package:flutter/widgets.dart';

class NavigationHistoryObserver extends NavigatorObserver {
final List<Route<dynamic>?> _history = <Route<dynamic>?>[];

/// Gets a clone of the navigation history as an immutable list.
XavierPaquet-Rapold marked this conversation as resolved.
Show resolved Hide resolved
BuiltList<Route<dynamic>> get history =>
BuiltList<Route<dynamic>>.from(_history);

/// Implements a singleton pattern for NavigationHistoryObserver.
static final NavigationHistoryObserver _singleton = NavigationHistoryObserver._internal();
factory NavigationHistoryObserver() {
return _singleton;
}
NavigationHistoryObserver._internal();

@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
_history.removeLast();
}

@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
_history.add(route);
}

@override
void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {
_history.remove(route);
}
}
30 changes: 29 additions & 1 deletion lib/features/app/navigation/navigation_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
// Project imports:
import 'package:notredame/features/app/analytics/analytics_service.dart';
import 'package:notredame/features/app/analytics/remote_config_service.dart';
import 'package:notredame/features/app/navigation/navigation_history_observer.dart';
import 'package:notredame/features/app/navigation/router_paths.dart';
import 'package:notredame/utils/locator.dart';

Expand All @@ -24,6 +25,9 @@ class NavigationService {
/// Will be used to report event and error.
final AnalyticsService _analyticsService = locator<AnalyticsService>();

/// Will be used to remove duplicate routes.
final NavigationHistoryObserver _navigationHistoryObserver = NavigationHistoryObserver();

GlobalKey<NavigatorState> get navigatorKey => _navigatorKey;

/// Pop the last route of the navigator if possible
Expand All @@ -37,7 +41,7 @@ class NavigationService {
return false;
}

/// Push a named route ([routeName] onto the navigator.
/// Push a named route [routeName] onto the navigator.
Future<dynamic> pushNamed(String routeName, {dynamic arguments}) {
final currentState = _navigatorKey.currentState;

Expand All @@ -53,6 +57,30 @@ class NavigationService {
return currentState.pushNamed(routeName, arguments: arguments);
}

/// Push a named route [routeName] onto the navigator
/// and remove existing routes with the same [routeName]
Future<dynamic> pushNamedAndRemoveDuplicates(String routeName, {dynamic arguments}) {
final currentState = _navigatorKey.currentState;

if (currentState == null) {
_analyticsService.logError(tag, "Navigator state is null");
return Future.error("Navigator state is null");
}

if (remoteConfigService.outage) {
return currentState.pushNamedAndRemoveUntil(
RouterPaths.serviceOutage, (route) => false);
}

final route = _navigationHistoryObserver.history
.where((r) => r.settings.name == routeName).firstOrNull;

if (route != null) {
currentState.removeRoute(route);
}
return currentState.pushNamed(routeName, arguments: arguments);
}

/// Replace the current route of the navigator by pushing the route named
/// [routeName] and then delete the stack of previous routes
Future<dynamic> pushNamedAndRemoveUntil(String routeName,
Expand Down
10 changes: 5 additions & 5 deletions lib/features/app/widgets/bottom_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,19 @@ class _BottomBarState extends State<BottomBar> {

switch (index) {
case BottomBar.dashboardView:
_navigationService.pushNamedAndRemoveUntil(RouterPaths.dashboard);
_navigationService.pushNamedAndRemoveDuplicates(RouterPaths.dashboard);
_analyticsService.logEvent("BottomBar", "DashboardView clicked");
case BottomBar.scheduleView:
_navigationService.pushNamedAndRemoveUntil(RouterPaths.schedule);
_navigationService.pushNamedAndRemoveDuplicates(RouterPaths.schedule);
_analyticsService.logEvent("BottomBar", "ScheduleView clicked");
case BottomBar.studentView:
_navigationService.pushNamedAndRemoveUntil(RouterPaths.student);
_navigationService.pushNamedAndRemoveDuplicates(RouterPaths.student);
_analyticsService.logEvent("BottomBar", "StudentView clicked");
case BottomBar.etsView:
_navigationService.pushNamedAndRemoveUntil(RouterPaths.ets);
_navigationService.pushNamedAndRemoveDuplicates(RouterPaths.ets);
_analyticsService.logEvent("BottomBar", "EtsView clicked");
case BottomBar.moreView:
_navigationService.pushNamedAndRemoveUntil(RouterPaths.more);
_navigationService.pushNamedAndRemoveDuplicates(RouterPaths.more);
_analyticsService.logEvent("BottomBar", "MoreView clicked");
}
_currentView = index;
Expand Down
10 changes: 5 additions & 5 deletions lib/features/app/widgets/navigation_rail.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,19 @@ class _NavRailState extends State<NavRail> {

switch (index) {
case NavRail.dashboardView:
_navigationService.pushNamedAndRemoveUntil(RouterPaths.dashboard);
_navigationService.pushNamedAndRemoveDuplicates(RouterPaths.dashboard);
_analyticsService.logEvent("BottomBar", "DashboardView clicked");
case NavRail.scheduleView:
_navigationService.pushNamedAndRemoveUntil(RouterPaths.schedule);
_navigationService.pushNamedAndRemoveDuplicates(RouterPaths.schedule);
_analyticsService.logEvent("BottomBar", "ScheduleView clicked");
case NavRail.studentView:
_navigationService.pushNamedAndRemoveUntil(RouterPaths.student);
_navigationService.pushNamedAndRemoveDuplicates(RouterPaths.student);
_analyticsService.logEvent("BottomBar", "StudentView clicked");
case NavRail.etsView:
_navigationService.pushNamedAndRemoveUntil(RouterPaths.ets);
_navigationService.pushNamedAndRemoveDuplicates(RouterPaths.ets);
_analyticsService.logEvent("BottomBar", "EtsView clicked");
case NavRail.moreView:
_navigationService.pushNamedAndRemoveUntil(RouterPaths.more);
_navigationService.pushNamedAndRemoveDuplicates(RouterPaths.more);
_analyticsService.logEvent("BottomBar", "MoreView clicked");
}
_currentView = index;
Expand Down
2 changes: 2 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import 'package:notredame/features/app/error/outage/outage_view.dart';
import 'package:notredame/features/app/integration/firebase_options.dart';
import 'package:notredame/features/app/navigation/navigation_service.dart';
import 'package:notredame/features/app/navigation/router.dart';
import 'package:notredame/features/app/navigation/navigation_history_observer.dart';
import 'package:notredame/features/app/startup/startup_view.dart';
import 'package:notredame/features/ets/events/api-client/hello_api_client.dart';
import 'package:notredame/features/more/feedback/models/custom_feedback_localization.dart';
Expand Down Expand Up @@ -108,6 +109,7 @@ class ETSMobile extends StatelessWidget {
navigatorKey: locator<NavigationService>().navigatorKey,
navigatorObservers: [
locator<AnalyticsService>().getAnalyticsObserver(),
NavigationHistoryObserver(),
],
home: outage ? OutageView() : StartUpView(),
onGenerateRoute: generateRoute,
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: The 4th generation of ÉTSMobile, the main gateway between the Éco
# pub.dev using `pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

version: 4.52.1+1
version: 4.52.2+1

environment:
sdk: '>=3.3.0 <4.0.0'
Expand Down
15 changes: 6 additions & 9 deletions test/features/app/widgets/bottom_bar_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ void main() {
await tester.tap(find.byIcon(Icons.school_outlined));
await tester.tap(find.byIcon(Icons.school_outlined));

verify(navigationServiceMock.pushNamedAndRemoveUntil(RouterPaths.student))
verify(navigationServiceMock.pushNamedAndRemoveDuplicates(RouterPaths.student))
.called(1);
});

Expand All @@ -71,8 +71,7 @@ void main() {
await tester.tap(find.byIcon(Icons.schedule_outlined));
await tester.tap(find.byIcon(Icons.dashboard_outlined));

verify(navigationServiceMock
.pushNamedAndRemoveUntil(RouterPaths.dashboard));
verify(navigationServiceMock.pushNamedAndRemoveDuplicates(RouterPaths.dashboard));
});

testWidgets('schedule', (WidgetTester tester) async {
Expand All @@ -82,8 +81,7 @@ void main() {

await tester.tap(find.byIcon(Icons.schedule_outlined));

verify(navigationServiceMock
.pushNamedAndRemoveUntil(RouterPaths.schedule));
verify(navigationServiceMock.pushNamedAndRemoveDuplicates(RouterPaths.schedule));
});

testWidgets('student', (WidgetTester tester) async {
Expand All @@ -93,8 +91,7 @@ void main() {

await tester.tap(find.byIcon(Icons.school_outlined));

verify(
navigationServiceMock.pushNamedAndRemoveUntil(RouterPaths.student));
verify(navigationServiceMock.pushNamedAndRemoveDuplicates(RouterPaths.student));
});

testWidgets('ets', (WidgetTester tester) async {
Expand All @@ -104,7 +101,7 @@ void main() {

await tester.tap(find.byIcon(Icons.account_balance_outlined));

verify(navigationServiceMock.pushNamedAndRemoveUntil(RouterPaths.ets));
verify(navigationServiceMock.pushNamedAndRemoveDuplicates(RouterPaths.ets));
});

testWidgets('more', (WidgetTester tester) async {
Expand All @@ -114,7 +111,7 @@ void main() {

await tester.tap(find.byIcon(Icons.menu_outlined));

verify(navigationServiceMock.pushNamedAndRemoveUntil(RouterPaths.more));
verify(navigationServiceMock.pushNamedAndRemoveDuplicates(RouterPaths.more));
});
});
});
Expand Down
Loading