NB (see comments below answer):
There is no bug, just bad usage by the issue reporter. It is caused because he didn’t pass the settings object along to the new MaterialPageRoute returned by the onGenerateRoute method (see the issue’s final comments). […]
Original answer mentionning a “bug”:
Without diving into any details those two properties do the same thing, but as @Alireza noted routes is checked first.
Also, using onGenerateRoute gives you a single place to add your custom business logic before pushing new routes (pages). For example, if you want to do some initializations.
routes property:
When a named route is pushed with Navigator.pushNamed, the route name
is looked up in this map. If the name is present, the associated
WidgetBuilder is used to construct a MaterialPageRoute that performs
an appropriate transition, including Hero animations, to the new
route.
onGenerateRoute property:
The route generator callback used when the app is navigated to a named
route.
…
This is used if routes does not contain the requested route.
IMPORTANT:
What you really want to be aware of is a known bug in onGenerateRoute property.
The problem is that if you use onGenerateRoute to create named routes you won’t be able to get the name of that route from RouteSettings object in your page. (Arguments attached to the settings object are fine though)
In other words:
Widget build(BuildContext context) {
ModalRoute.of(context).settings.name == null; //bug
ModalRoute.of(context).settings.arguments != null; //ok
...
This may affect you in case you would like to know the name of the current route.
For example, if you want to pop some screens:
navigator.popUntil(ModalRoute.withName('/login'));
Therefore, until this problem is resolved, I recommend using the routes: property.