I'm using navigator 2.0 and all is well with the default URL strategy ('#'). I have a singleton service that loads data and this singleton is called only once no matter the routes.
However, when I change the URL strategy either by including url_strategy: ^0.2.0 as a dependency or following the steps at https://flutter.dev/docs/development/ui/navigation/url-strategies, the app reloads, and my singleton instantiates all over (and the expensive load operations happen again).
Has anyone else experienced this or know a solution?
As an example, below loads with initial route of / and displays "the number is 0 (zero)." and the URL is updated to /0. Pressing the increment button shows everything in sync (URL and screen). Manually entering a new route works fine and the StateService global variable is instantiated just one (have a print line of "*** in StateService() constructor ***" to show this). But if you uncomment out "setPathUrlStrategy()" in function main() and then try again, you'll see that manually entering a new route causes the StateService to be instantiated all over again. This will obviously kill performance for apps doing work upon startup.
Any ideas?
import 'package:flutter/material.dart';
import 'package:url_strategy/url_strategy.dart';
void main() {
// setPathUrlStrategy(); // causes app to reload when URL manually changed; leaving out doesn't
runApp(MyApp());
}
// below sb a Singleton; will just instatiate a global variable once to simulate...
class StateService {
int number = 0;
List<String> words = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven"];
StateService() {
print("*** in StateService() constructor.... ***");
}
void increaseNumber() {
number++;
}
String getWord(int number) {
if (number < 0) return "unknown";
if (number > words.length - 1) return "unknown";
return words[number];
}
}
// display the number and its word (if 0-11); has button to increment value
class DisplayNumberScreen extends StatelessWidget {
final VoidCallback increase;
final int number;
DisplayNumberScreen({Key? key, required this.number, required this.increase}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text("The number is: $number (${globalState.getWord(number)})")),
floatingActionButton: FloatingActionButton(
onPressed: increase,
tooltip: "Add 1",
child: Icon(Icons.add),
),
);
}
}
class NavigationState {
final int value;
NavigationState({required this.value});
}
class UrlHandlerRouterDelegate extends RouterDelegate<NavigationState> with ChangeNotifier, PopNavigatorRouterDelegateMixin {
@override
GlobalKey<NavigatorState> get navigatorKey => urlHandlerRouterDelegateNavigatorKey;
void _increaseNumberHandler() {
globalState.increaseNumber();
notifyListeners();
}
// App state to Navigation state, triggered by notifyListeners()
@override
NavigationState get currentConfiguration => NavigationState(value: globalState.number);
@override
Widget build(BuildContext context) {
return Navigator(
pages: [
MaterialPage(child: DisplayNumberScreen(number: globalState.number, increase: _increaseNumberHandler)),
],
onPopPage: (_, __) {
return false;
},
);
}
// Navigation state to app state
@override
Future<void> setNewRoutePath(NavigationState navigationState) async {
globalState.number = navigationState.value;
}
}
class UrlHandlerInformationParser extends RouteInformationParser<NavigationState> {
// URL to navigation state; note initial route of '/' will result in 0 as will any route that's not a number
@override
Future<NavigationState> parseRouteInformation(RouteInformation routeInformation) async =>
NavigationState(value: int.tryParse(routeInformation.location!.substring(1)) ?? 0); // substring(1) to remove the leading '/'
// Navigation state to URL
@override
RouteInformation restoreRouteInformation(NavigationState navigationState) => RouteInformation(location: '/${navigationState.value}');
}
// Assign global variables
final GlobalKey<NavigatorState> urlHandlerRouterDelegateNavigatorKey = GlobalKey<NavigatorState>();
final globalState = StateService();
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: UrlHandlerRouterDelegate(),
routeInformationParser: UrlHandlerInformationParser(),
);
}
}