I have an architectural question.

First of all, I understand that Navigator 2.0 enables updating the URL path of the app. For Flutter web applications and for deep linking on iOS and Android, this can obviously be very important in certain situations. However, most applications probably don't need the amount of complexity involved in implementing URL sync with Navigator 2.0.

So, that brings us back to navigation with Navigator 1.0. The issue I have with the approach they require is that implementation is like the opposite of what you'd achieve from dependency injection in terms of how it affects life cycle management and control flow. By forcing construction of objects (in this case widgets) to occur deep in your code, it sort of convolutes your architecture... For example, instead of having an easy to follow hierarchy, you now have much greater cyclomatic complexity as you also need to worry about managing navigation state and controlling application flow from a second dimension (instead of just leveraging the hierarchy.) To me, it seems like it would violate SOLID to allow a deeply nested child to change which extended relatives are displayed because it seems like that relative's rendering behavior should be controlled by its parent, not some random other widget in some unknown part of the hierarchy. As an analogy, that would be like my child calling their second cousin to take them out of school instead of that decision (for the cousin to go to school or not) being made by the cousin's parent.

In contrast, by leveraging stateful widgets, screens and child widgets can be conditionally displayed without relying on Navigation. This approach makes the hierarchy clean and easy to read and follow. If you need to troubleshoot the display of a widget, you only need to walk up the hierarchy (and occasionally back down to a sibling, depending on how you're propagating state.) By using the Provider architecture (like StreamProvider and ChangeNotifier), state and changes can be gracefully propagated throughout the application. So, a child deep in the hierarchy could use a model class to propagate a state change to an observer that conditionally displays child widgets in a reactive manner. (Going back to the analogy, that would be like my child broadcasting that there was a family emergency, and their second cousin's parent reacts to that by pulling their child out of school that day.)

This reactive design allows cleaner separation of concerns because a descendent never needs to worry about how some extended relative wants to react to a state change (such as by displaying a different screen.) It also makes debugging and testing a lot simpler.

With all that said, it does appear that Navigation is required to get proper behavior of the back button on a device. Is that the only redeeming value of using Navigation? Can someone please help me understand if I'm missing something?

Also, if that's the main purpose of using Navigation, is there a principle I can follow to use it without turning my control flow into a spaghetti mess?