Flutter:继承的小部件和路由

Posted

技术标签:

【中文标题】Flutter:继承的小部件和路由【英文标题】:Flutter: Inherited Widget and Routes 【发布时间】:2018-10-02 18:36:15 【问题描述】:

我想在我的应用程序的根目录中拥有一个继承的小部件,它将包含我将在整个应用程序中使用的数据提供程序。所以我有这个继承的小部件,但每次我尝试加载它时,我都会得到这个The getter 'data' was called on null,我不知道为什么。

这是我的 main.dart:

void main() => runApp(new MatAppRoot());

class MatAppRoot extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new MaterialApp(
      title: 'MyCoolApp',
      routes: <String, WidgetBuilder>
        'Login': (BuildContext context) => new LoginPage(),
        'Cool': (BuildContext context) => new CoolPage(),
      ,
      home: new CoolApp(),
    );
  


class CoolAppextends StatefulWidget 

  final Widget child;

  CoolApp(this.child);

  @override
  CoolAppState createState() => new CoolAppState();

  static CoolAppState of(BuildContext context) 
    return (context.inheritFromWidgetOfExactType(CoolInherit) as CoolInherit).data;
  


class CoolAppState extends State<CoolApp> 
  String randomString = 'AYEEAS!!!';

  @override
  void initState()  super.initState();
    Navigator.of(context).pushNamedAndRemoveUntil('Login', (Route<dynamic> route) => false);
  

  @override
  Widget build(BuildContext context) 
    return new CoolInherit(
      data: this,
      child: new LoginPage(),
    );
  



class CoolInherit extends InheritedWidget 

  final CoolAppState data;

  CoolInherit(
    Key key,
    this.data,
    Widget child,
  ): super(
    key: key,
    child: child
  );

  @override
  bool updateShouldNotify(CoolInherit old) 
    return true;
  

然后我的LoginPage 基本上在登录后重定向如下:

if (logInSuccessful) 
  Navigator.of(context).pushNamedAndRemoveUntil('Cool', (Route<dynamic> route) => false);

在我的Cool 页面中,我尝试在单击这样的按钮时加载另一个页面:

viewCoolDetails() 
  Navigator.push(
    context,
    new MaterialPageRoute(builder: (context) => new CoolDetailsPage()),
  );

但在我的CoolDetailsPage 中,当我这样做时它会崩溃:

@override
Widget build(BuildContext context) 
  final inheritedWidget = CoolApp.of(context);
  print(inheritedWidget.randomString);   <-- ERROR: The getter 'data' was called on null
  return new Text('Cool!');

错误详情:

I/flutter ( 6129): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 6129): The following NoSuchMethodError was thrown building CoolDetailsPage(dirty, state:
I/flutter ( 6129): _CoolDetailsPage#ba0bb):
I/flutter ( 6129): The getter 'data' was called on null.
I/flutter ( 6129): Receiver: null
I/flutter ( 6129): Tried calling: data
I/flutter ( 6129):
I/flutter ( 6129): When the exception was thrown, this was the stack:
I/flutter ( 6129): #0      Object.noSuchMethod (dart:core/runtime/libobject_patch.dart:46:5)
I/flutter ( 6129): #1      CoolApp.of (/lib/main.dart:56:83)
... etc etc

main.dart:56 是return (context.inheritFromWidgetOfExactType(CoolInherit) as CoolInherit).data;,所以如果我的侦探工作达到标准,我怀疑这与导航/上下文有关,这会阻止我的最终小部件访问继承的小部件,但我不是确定。

更新:

我能说的最好的,我需要在更高级别插入我的 InheritedWidget;在导航仪之前。所以我将它插入到 MaterialApp 中:

builder: (context, child) 
  return new CoolApp(child: child);
,

但这似乎不起作用......

E/flutter (32321): [ERROR:topaz/lib/tonic/logging/dart_error.cc(16)] Unhandled exception:
E/flutter (32321): Navigator operation requested with a context that does not include a Navigator.
E/flutter (32321): The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
E/flutter (32321): #0      Navigator.of.<anonymous closure> (package:flutter/src/widgets/navigator.dart:1180:9)
E/flutter (32321): #1      Navigator.of (package:flutter/src/widgets/navigator.dart:1187:6)

【问题讨论】:

【参考方案1】:

我长期遇到同样的问题,我意识到如果您使用 Inherited Widget 包装 MaterialApp,您的数据可以通过整个应用程序访问。但在您的情况下,您需要在用户登录后传递数据,因此您需要创建一个新的 Navigator 并用您的 Inherited Widget 包装它。您可以看到这个项目 https://github.com/johnstef99/inherited-widget-demo

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'InheritedWidget Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyNav(),
    );
  


Route generatePage(child) 
  return MaterialPageRoute(builder: (context) => child);


class MyNav extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MyData(
      data: 'omg',
      child: Navigator(
        onGenerateRoute: (settings) 
          switch (settings.name) 
            case 'page1':
              return generatePage(PageOne());
            case 'page2':
              return generatePage(PageTwo());
            case 'page3':
              return generatePage(PageThree());
          
        ,
        initialRoute: 'page1',
      ),
    );
  


class MyData extends InheritedWidget 
  MyData(Key key, this.child, this.data) : super(key: key, child: child);

  final Widget child;
  final String data;

  static MyData of(BuildContext context) 
    return (context.inheritFromWidgetOfExactType(MyData) as MyData);
  

  @override
  bool updateShouldNotify(MyData oldWidget) 
    return true;
  


class PageOne extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: Text('Page 1'),
      ),
      backgroundColor: Colors.red,
      body: RaisedButton(
        child: Text("Goto page 2, data=$MyData.of(context).data"),
        onPressed: () 
          Navigator.of(context)
              .push(MaterialPageRoute(builder: (_) => PageTwo()));
        ,
      ),
    );
  


class PageTwo extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: Text('Page 2'),
      ),
      backgroundColor: Colors.red,
      body: RaisedButton(
        child: Text("Goto page 3, data=$MyData.of(context).data"),
        onPressed: () 
          Navigator.of(context)
              .push(MaterialPageRoute(builder: (_) => PageThree()));
        ,
      ),
    );
  


class PageThree extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: Text('Page 3'),
      ),
      backgroundColor: Colors.green,
      body: RaisedButton(
        child: Text("Goto page 4, data=$MyData.of(context).data"),
        onPressed: null,
      ),
    );
  

【讨论】:

你是对的。但是,如果您想使用ModalRoute.of(context).settings.arguments 获取路由参数,则在我的情况下不起作用。它返回null【参考方案2】:

那是因为您试图从另一条路由(动态)访问位于路由/ 中的CoolApp

但在您的动态路由中,没有CoolApp。所以CoolApp.of(context) 返回null,因此访问.data 会崩溃。

您需要找到一种方法在您的新路由中拥有一个 CoolApp 实例。

更多信息请关注Get access to the context of InheritedWidget

【讨论】:

这很有道理...这是我一直坚持的部分。不知道如何将其纳入新路线。我在您引用的另一个问题中看到他们谈论MaterialApp builder,但不确定在这种情况下我将如何实现它,或者它是否是我需要的。 对此仍然感到困惑......似乎无法在网上找到任何关于我如何以一种有效的方式做这样的事情...... 我在 MaterialApp 中添加了builder: (context, child) return new CoolApp(child: child);,,因为据我所知,这将在导航器之前注入状态......但这似乎没有帮助...... :( 在这里找到了一个很好的方法:http://ericwindmill.com/using-flutter-inherited-widgets-effectively @Jus10 你找到解决办法了吗?

以上是关于Flutter:继承的小部件和路由的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 上的小部件的 onResume() 和 onPause()

Flutter/Dart:我的小部件树中可以有多个带有状态的小部件吗?

Flutter:有状态的小部件不更新

使用继承的小部件传递小部件键

Flutter 小部件测试点击 - 不会在指定的小部件上进行测试

Flutter ListView 与不同的小部件和列表项