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

I have a StatefulWidget which I want to use in named route. I have to pass some arguments which I am doing as suggested in https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments i.e.

Navigator.pushNamed(
      context,
      routeName,
      arguments: <args>,
    );

Now, I need to access these argument's in the state's initState method as the arguments are needed to subscribe to some external events. If I put the args = ModalRoute.of(context).settings.arguments; call in initState, I get a runtime exception.

20:49:44.129 4 info flutter.tools I/flutter ( 2680): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
20:49:44.129 5 info flutter.tools I/flutter ( 2680): The following assertion was thrown building Builder:
20:49:44.129 6 info flutter.tools I/flutter ( 2680): inheritFromWidgetOfExactType(_ModalScopeStatus) or inheritFromElement() was called before
20:49:44.130 7 info flutter.tools I/flutter ( 2680): _CourseCohortScreenState.initState() completed.
20:49:44.130 8 info flutter.tools I/flutter ( 2680): When an inherited widget changes, for example if the value of Theme.of() changes, its dependent
20:49:44.131 9 info flutter.tools I/flutter ( 2680): widgets are rebuilt. If the dependent widget's reference to the inherited widget is in a constructor
20:49:44.131 10 info flutter.tools I/flutter ( 2680): or an initState() method, then the rebuilt dependent widget will not reflect the changes in the
20:49:44.131 11 info flutter.tools I/flutter ( 2680): inherited widget.
20:49:44.138 12 info flutter.tools I/flutter ( 2680): Typically references to inherited widgets should occur in widget build() methods. Alternatively,
20:49:44.138 13 info flutter.tools I/flutter ( 2680): initialization based on inherited widgets can be placed in the didChangeDependencies method, which
20:49:44.138 14 info flutter.tools I/flutter ( 2680): is called after initState and whenever the dependencies change thereafter.
20:49:44.138 15 info flutter.tools I/flutter ( 2680): 
20:49:44.138 16 info flutter.tools I/flutter ( 2680): When the exception was thrown, this was the stack:
20:49:44.147 17 info flutter.tools I/flutter ( 2680): #0      StatefulElement.inheritFromElement.<anonymous closure> (package:flutter/src/widgets/framework.dart:3936:9)
20:49:44.147 18 info flutter.tools I/flutter ( 2680): #1      StatefulElement.inheritFromElement (package:flutter/src/widgets/framework.dart:3969:6)
20:49:44.147 19 info flutter.tools I/flutter ( 2680): #2      Element.inheritFromWidgetOfExactType (package:flutter/src/widgets/framework.dart:3285:14)
20:49:44.147 20 info flutter.tools I/flutter ( 2680): #3      ModalRoute.of (package:flutter/src/widgets/routes.dart:698:46)
20:49:44.147 21 info flutter.tools I/flutter ( 2680): #4      _CourseCohortScreenState.initState.<anonymous closure> (package:esk2/cohort_screen.dart:57:23)

I do not want to put that logic in build method as build could be called multiple times and the initialization needs to happen only once. I could put the entire logic in a block with a boolean isInitialized flag, but that does not seem like the right way of doing this. Is this requirement/case not supported in flutter as of now?


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())));