Flutter:在小部件的状态initState中从Navigator获取传递的参数

时间:2019-05-22 18:10:25

标签: flutter

我有一个StatefulWidget,我想在命名路由中使用它。我必须按照https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments中的建议,即

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

现在,我需要在状态的initState方法中访问这些参数,因为需要这些参数来订阅某些外部事件。如果将args = ModalRoute.of(context).settings.arguments;调用放在initState中,则会收到运行时异常。

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)

我不想将此逻辑放入build方法中,因为build可以被多次调用,并且初始化只需发生一次。我可以将整个逻辑放在带有boolean isInitialized标志的块中,但这似乎并不是正确的方法。   到目前为止,此要求/案例是否不受支持?

7 个答案:

答案 0 :(得分:9)

我和您遇到了同样的问题,并提出了解决方案。代替使用onGenerateRoute,您仍然可以使用pushNamed Navigator来传递参数,并且仍然可以在initState中访问ModalRoute参数-方法如下:

1)使用initState中的future获得对上下文的访问。

  • 您可以使用Future.delayed(Duration.zero, () {} )
  • 这使您可以访问上下文,还可以使用initState来执行诸如showDialog之类的操作,因为您可以在build方法之外访问上下文。

2)使用ModalRoute.of(context).settings.arguments

提取参数
  • 在将来,请提取参数并将其存储在您在initState之前创建的,但显然仍在State对象中的已声明但未初始化的变量中。
  • 一旦有了参数,就可以对它们进行任何操作,例如将变量传递给函数。
  • 重要说明:您必须在future函数体内部使用变量,否则Flutter将跳过future(按其编程方式进行)并先完成外部的内容,因此var仍将返回null,因为future还没有决定给var一个值。

全部看起来像这样:

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

答案 1 :(得分:2)

像这样使用MaterialApp.onGenerateRoute属性:

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

现在您可以简单地使用NavigatorState.pushNamed

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

这里有一些测试Hello小部件:

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

答案 2 :(得分:1)

恕我直言,接受的应该是 didChangeDependencies

late Object args; 

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

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

它在 docs

中提到 <块引用>

这个方法也会在 initState 之后立即调用。这是安全的 从此方法调用 BuildContext.dependOnInheritedWidgetOfExactType。

您的错误代码中也提到了这一点

<块引用>

基于继承的widget的初始化可以放在didChangeDependencies中 在 initState 之后以及每当依赖项发生变化时调用 此后。

答案 3 :(得分:0)

您可以通过新的PageRoute调用push,而不是通过pushNamed发送参数。

假设您的参数类型称为Argument。这是您的有状态窗口小部件及其状态类的样子:

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"

    }
}

这是您使用PageRoute调用推送的方式:

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

答案 4 :(得分:0)

如果routeName不能导入太多,这对我来说是个好方法

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

然后在您的小部件中,显然这只是在initState上使用TextEditingController的愚蠢用例:

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

答案 5 :(得分:0)

您可以使用在构造函数中传递de参数的命名路由。

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

然后在您的窗口小部件中。

 class Hello extends StatefulWidget {
  final argument;

  MySecondaryPage({this.argument});

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

答案 6 :(得分:0)

我使用 WidgetsBinding 来实现。它可以在 initState 内部调用,并且只会在 Build widgets 渲染完成后调用一次。

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