Flutter: changing the URL strategy results in the app reloading

Viewed 421

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(),
    );
  }
}
0 Answers
Related