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

Flutter

Flutter Problem Overview


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?

Flutter Solutions


Solution 1 - Flutter

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,
        ),
      ),
    );
  }
}

Solution 2 - Flutter

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.
  1. 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);
    });
  }

Solution 3 - Flutter

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

Solution 4 - Flutter

IMHO The accepted should be didChangeDependencies.

late Object args; 

@override
void didChangeDependencies() {
  args = ModalRoute.of(context).settings.argument
  super.didChangeDependencies();
}

@override
Widget build(BuildContext context) {
   /// use args here 
}

It's mentioned in the docs

> This method is also called immediately after initState. It is safe to > call BuildContext.dependOnInheritedWidgetOfExactType from this method.

It's also mentioned in your error code

> initialization based on inherited widgets can be placed in the didChangeDependencies is > called after initState and whenever the dependencies change > thereafter.

Solution 5 - Flutter

You can use named routes passing de argument in constructor.

   routes: {
    '/hello': (context) => Hello(
          argument: ModalRoute.of(context).settings.arguments,
        ),
  },

Then in your widget.

 class Hello extends StatefulWidget {
  final argument;

  Hello({this.argument});

  @override
  _HelloState createState() => _HelloState();
}

Solution 6 - Flutter

I do it with WidgetsBinding. It can be called inside initState, and will be called only once after Build widgets done with rendering.

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

   final widgetsBinding = WidgetsBinding.instance;
   widgetsBinding.addPostFrameCallback((callback) {
  if (ModalRoute.of(context).settings.arguments != null) {
    _currentIndex = ModalRoute.of(context).settings.arguments;
  }
 });
}

Solution 7 - Flutter

If the routeName doesnt import that much this is a great way to me

Navigator.push(
    context,
    MaterialPageRoute(
        builder: (_) => MySecondaryPage(requiredAttrib: myValue),
    ),
);

Then in your Widget, clearly this is just a dumb use case with the TextEditingController on the initState:

class MySecondaryPage extends StatefulWidget {
  final requiredAttrib;

  MySecondaryPage({this.requiredAttrib});

  @override
  _MySecondaryPageState createState() => _MySecondaryPageState();
}

class _MySecondaryPageState extends State<MySecondaryPage> {

    TextEditingController _myController = TextEditingController();

    @override
    void initState() { 
      super.initState();
      _myController.text = widget.requiredAttrib;
      ...
    }

  @override
  Widget build(BuildContext context) {
    return TextField(
        controller: _myController,
        ...
    );
  }
}

Solution 8 - Flutter

Use this in initState. This will load the arguments which you get from the previous page in the initState of your current page.

   Future.delayed(Duration.zero, () {
  resetState(); //it is just a function where i am pasisng my obtained route data
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionadarshView Question on Stackoverflow
Solution 1 - FlutterpskinkView Answer on Stackoverflow
Solution 2 - FlutterMarviosoView Answer on Stackoverflow
Solution 3 - FlutterChunlong LiView Answer on Stackoverflow
Solution 4 - FlutterlenzView Answer on Stackoverflow
Solution 5 - FlutterDiego Martins VieiraView Answer on Stackoverflow
Solution 6 - FlutterSalmonView Answer on Stackoverflow
Solution 7 - FlutterAlejandro Sanchez DuranView Answer on Stackoverflow
Solution 8 - FlutterSyed Abdul HannanView Answer on Stackoverflow