Flutter: Get passed arguments from Navigator in Widget's state's initState

use MaterialApp.onGenerateRoute property like this:

onGenerateRoute: (RouteSettings settings) {
  print('build route for ${settings.name}');
  var routes = <String, WidgetBuilder>{
    "hello": (ctx) => Hello(settings.arguments),
    "other": (ctx) => SomeWidget(),
  };
  WidgetBuilder builder = routes[settings.name];
  return MaterialPageRoute(builder: (ctx) => builder(ctx));
},

now you can simply use NavigatorState.pushNamed:

Navigator.of(context).pushNamed("hello", arguments: "world");

here you have some test Hello widget:

class Hello extends StatelessWidget {
  final String greet;

  Hello(this.greet);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Text(
          'hello $greet',
          textScaleFactor: 5.0,
        ),
      ),
    );
  }
}

I just had the same problem as you and put a solution together. Instead of using onGenerateRoute, you can still use pushNamed Navigator to pass arguments and you can still access the ModalRoute arguments in initState - and here's how:

1) Use a future in initState to gain access to the context.

  • You can do this with Future.delayed(Duration.zero, () {} )
  • This gives you access to context and you can also do things like showDialog in initState using this because you can access the context here outside of the build method.

2) Extract the arguments using ModalRoute.of(context).settings.arguments

  • Inside the future, extract the arguments and store them in a declared, but un-initialised variable that you made before initState but obviously still in the State object.
  • Once you have the arguments you can do whatever you want with them, like passing the variable into a function perhaps.
  • Important note: you have to use the variable inside of the future function body, otherwise Flutter will skip over the future (as its programmed to do) and complete whatever is outside first, so you var will still return null because the future hasn't resolved to give the var a value yet.

All together it would look like this:

var = args;
_yourFunction(args) async {
// whatever you want to do
}

@override
  void initState() {
    super.initState();
    // future that allows us to access context. function is called inside the future
    // otherwise it would be skipped and args would return null
    Future.delayed(Duration.zero, () {
      setState(() {
        args = ModalRoute.of(context).settings.arguments;
      });
      print(args['id']);
      _yourFunction(args);
    });
  }


Instead of sending arguments through pushNamed, you could call push with a new PageRoute.

Suppose your argument type is called Argument. Here is what your stateful widget and its state classes look like:

class YourStatefulWidget extends StatefulWidget {
    final Argument argument;

    YourStatefulWidget({
        @required this.argument,
    });

    @override
    State<StatefulWidget> createState() {
        return YourStatefulWidgetState();
    }
}

class YourStatefulWidgetState extends State<YourStatefulWidget> {

    @override
    initState() {
        super.initState();

        // Refer to your argument here by "widget.argument"

    }
}

Here is how you call push with a PageRoute:

Navigator.of(context).push(MaterialPageRoute(builder: (context) => YourStatefulWidget(argument: Argument())));

Tags:

Flutter