【问题标题】:Is it possible to access instance function that is within Stateful Widget from another widget?是否可以从另一个小部件访问 Stateful Widget 中的实例函数?
【发布时间】:2021-08-23 02:30:54
【问题描述】:

我是 Flutter/dart 和响应式编程的新手,我一直在尝试解决这个问题。我在 DartPad 中构建了附加的测试代码,以确定我想在我的应用程序中做什么。我创建了一个有状态的小部件(DataRow),在它的状态对象(_dataRowState)中我有一个函数(setAndRefresher)。我正在尝试从不同的小部件(在 ElevatedButton 的 onPressed: 中)从小部件外部访问此功能:

datums[0].setAndRefresh!(datums[0].count + 1.0);

我已经建立了一个名为 Datum 的数据对象的全局列表,其中有一个名为 setAndRefresh 的回调成员。我在创建状态小部件时将列表中特定数据的索引传递给它,并使用它尝试将回调函数存储到该数据对象中:

基准[_datumIndex].setAndRefresh = setAndRefresher;

我确定我错过了什么,但不知道是什么。我正在尝试做的事情是否被阻止,如果是,为什么?我正在努力学习这门语言,就像我在努力解决这个问题一样。

import 'package:flutter/material.dart';

class Datum extends Object {
  /// data storage object
  double count = 0;
  String word = 'Uninitialized';
  Function? setAndRefresh;

  void prt() {
    print('word: $word – count: $count – callback: $setAndRefresh');
  }

  void incrementCount() {
    print('incrementCount() -> $count');
    count = count + 1;
    print('                 -> $count');
  }
}

List<Datum> datums = [];

/// =============================================

void main() {
  // Create three datum in a list
  datums.add(Datum());
  datums.add(Datum());
  datums.add(Datum());
  for (Datum dt in datums) {
    dt.prt();
  }
  datums[0].word = 'All buttons total';
  datums[1].word = 'Button 1 count';
  datums[2].word = 'Button 2 count';
  for (Datum dt in datums) {
    dt.prt();
  }
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Testing',
      theme: ThemeData(
        // primarySwatch: Colors.blue,
        backgroundColor: Colors.green,
      ),
      home: Scaffold(
        appBar: AppBar(
          brightness: Brightness.dark,
          backgroundColor: Colors.green,
        ),
        body: Column(
          // mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RichText(
              text: const TextSpan(
                text: 'Test Buttons',
              ),
            ),
            const DataRow(datumIndex: 0),
            const DataRow(datumIndex: 1),
            const DataRow(datumIndex: 2),
          ],
        ),
      ),
    );
  }
}

/// =============================================

class DataRow extends StatefulWidget {
  const DataRow({required this.datumIndex});
  final int datumIndex;

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

class _DataRowState extends State<DataRow> {
  late int _datumIndex;
  double count = 0;

  @override
  void initState() {
    super.initState();
    _datumIndex = widget.datumIndex;
    print('Init setAndRefresh for $_datumIndex – callback: ${datums[_datumIndex].setAndRefresh}');
    datums[_datumIndex].setAndRefresh = setAndRefresher;
    print('Done init setAndRefresh for $_datumIndex – callback: ${datums[_datumIndex].setAndRefresh}');
  }

  void setAndRefresher({required double count}) {
    print('Did set $count and refresh.');
    setState(() {
      datums[_datumIndex].count = count;
      this.count = count;
    });
  }

  @override
  dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return lineItem(
      name: '${datums[_datumIndex].word} is now: ${datums[_datumIndex].count}  ',
      color: Colors.orange,
    );
  }

  Widget lineItem({required String name, required Color color}) {
    return Container(
      width: 500,
      height: 50,
      color: color,
      child: Row(
        children: <Widget>[
          RichText(
            text: TextSpan(text: name),
          ),
          ElevatedButton(
            onPressed: () {
              print('Pressed Button $_datumIndex – callback: ${datums[_datumIndex].setAndRefresh}');
              datums[_datumIndex].incrementCount();
              setState(() {
                count = datums[_datumIndex].count;
              });
              if (_datumIndex != 0 && datums[0].setAndRefresh != null) {
                print('Now, increase All buttons total!()');      // This does print
                datums[0].setAndRefresh!(datums[0].count + 1.0);  // This is problem line?
                print('Did it');                                  // this never prints
              }
            },
            child: Text('Button ${widget.datumIndex}'),
          ),
        ],
      ),
    );
  }
}

此测试代码创建三个按钮。单击第一个按钮似乎可以工作,但是单击其他按钮中的任何一个都可以工作一次,然后该按钮似乎冻结了。以下是再次单击 Button1、Button0 和 Button0 所产生的控制台输出:

word: Uninitialized – count: 0 – callback: null
word: Uninitialized – count: 0 – callback: null
word: Uninitialized – count: 0 – callback: null
word: All buttons total – count: 0 – callback: null
word: Button 1 count – count: 0 – callback: null
word: Button 2 count – count: 0 – callback: null
Init setAndRefresh for 0 – callback: null
Done init setAndRefresh for 0 – callback: Closure: ({required double count}) => void from: function setAndRefresher() {
    [native code]
}
Init setAndRefresh for 1 – callback: null
Done init setAndRefresh for 1 – callback: Closure: ({required double count}) => void from: function setAndRefresher() {
    [native code]
}
Init setAndRefresh for 2 – callback: null
Done init setAndRefresh for 2 – callback: Closure: ({required double count}) => void from: function setAndRefresher() {
    [native code]
}
Script error.
Pressed Button 1 – callback: Closure: ({required double count}) => void from: function setAndRefresher() {
    [native code]
}
incrementCount() -> 0
                 -> 1
Now, increase All buttons total!()
Script error.
Pressed Button 0 – callback: Closure: ({required double count}) => void from: function setAndRefresher() {
    [native code]
}
incrementCount() -> 0
                 -> 1
Pressed Button 0 – callback: Closure: ({required double count}) => void from: function setAndRefresher() {
    [native code]
}
incrementCount() -> 1
                 -> 2

【问题讨论】:

    标签: flutter function dart callback instance


    【解决方案1】:

    而不是使用全局变量来做这些事情。 Flutter 为你提供了一个 Key 类,可以用来做你想做的事情。

    import 'package:flutter/material.dart';
    
    class Datum extends Object {
      /// data storage object
      double count = 0;
      String word = 'Uninitialized';
      Function? setAndRefresh;
    
      void prt() {
        print('word: $word – count: $count – callback: $setAndRefresh');
      }
    
      void incrementCount() {
        print('incrementCount() -> $count');
        count = count + 1;
        print('                 -> $count');
      }
    }
    
    List<Datum> datums = [];
    
    /// =============================================
    
    void main() {
      // Create three datum in a list
      datums.add(Datum());
      datums.add(Datum());
      datums.add(Datum());
      for (Datum dt in datums) {
        dt.prt();
      }
      datums[0].word = 'All buttons total';
      datums[1].word = 'Button 1 count';
      datums[2].word = 'Button 2 count';
      for (Datum dt in datums) {
        dt.prt();
      }
      runApp(MyApp());
    }
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      
      final zeroKey = GlobalKey<_DataRowState>();
      
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Testing',
          theme: ThemeData(
            // primarySwatch: Colors.blue,
            backgroundColor: Colors.green,
          ),
          home: Scaffold(
            appBar: AppBar(
              brightness: Brightness.dark,
              backgroundColor: Colors.green,
            ),
            body: Column(
              // mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                RichText(
                  text: const TextSpan(
                    text: 'Test Buttons',
                  ),
                ),
                DataRow(datumIndex: 0, key: zeroKey),
                DataRow(datumIndex: 1, zeroKey: zeroKey),
                DataRow(datumIndex: 2, zeroKey: zeroKey),
              ],
            ),
          ),
        );
      }
    }
    
    /// =============================================
    
    class DataRow extends StatefulWidget {
      final int datumIndex;
      final GlobalKey<_DataRowState>? zeroKey;
    
      const DataRow({Key? key, this.zeroKey, required this.datumIndex}) : super(key: key);
    
      @override
      _DataRowState createState() => _DataRowState();
    }
    
    class _DataRowState extends State<DataRow> {
      late int _datumIndex;
      double count = 0;
    
      @override
      void initState() {
        super.initState();
        _datumIndex = widget.datumIndex;
        print('Init setAndRefresh for $_datumIndex – callback: ${datums[_datumIndex].setAndRefresh}');
        datums[_datumIndex].setAndRefresh = setAndRefresher;
        print('Done init setAndRefresh for $_datumIndex – callback: ${datums[_datumIndex].setAndRefresh}');
      }
    
      void setAndRefresher({required double count}) {
        print('Did set $count and refresh.');
        setState(() {
          datums[_datumIndex].count = count;
          this.count = count;
        });
      }
    
      @override
      dispose() {
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return lineItem(
          name: '${datums[_datumIndex].word} is now: ${datums[_datumIndex].count}  ',
          color: Colors.orange,
        );
      }
    
      Widget lineItem({required String name, required Color color}) {
        return Container(
          width: 500,
          height: 50,
          color: color,
          child: Row(
            children: <Widget>[
              RichText(
                text: TextSpan(text: name),
              ),
              ElevatedButton(
                onPressed: () {
                  print('Pressed Button $_datumIndex – callback: ${datums[_datumIndex].setAndRefresh}');
                  datums[_datumIndex].incrementCount();
                  setState(() {
                    count = datums[_datumIndex].count;
                  });
                  if(widget.zeroKey != null) {
                    print('Now, increase All buttons total!()');      // This does print
                    
                    widget.zeroKey?.currentState?.setAndRefresher(count: datums[0].count + 1.0);  // This is problem line?
                    print('Did it');
                  }
    //               if (_datumIndex != 0 && datums[0].setAndRefresh != null) {
    //                                                   // this never prints
    //               }
                },
                child: Text('Button ${widget.datumIndex}'),
              ),
            ],
          ),
        );
      }
    }
    

    传递给第一个 DataRow() 的零键将用作控制该小部件状态的键。然后对于第二个和第三个 DataRow() 我们再次传递 zeroKey 但作为一个不同的参数表明我们不想控制它们的状态。然后,如果 _DataRowState() 中的 zeroKey 不为空,我们可以使用 zeroKey,而不是使用全局变量。

    注意 zeroKey 的传递方式不同:

    [
      ...
      DataRow(datumIndex: 0, key: zeroKey),
      DataRow(datumIndex: 1, zeroKey: zeroKey),
      DataRow(datumIndex: 2, zeroKey: zeroKey),
      ...
    ]
    

    DataRow(datumIndex: 0, key: zeroKey) 第一个 DataRow 的关键参数是说 zeroKey 绑定到第一个小部件。

    DataRow(datumIndex: 1,zeroKey: zeroKey) 第二个和第三个的 zeroKey 参数表示它只是另一个参数,并且 zeroKey 不应绑定到这些小部件的状态。

    【讨论】:

    • 这太棒了!但是您能否澄清一下,如果我希望能够从其他小部件中访问/更新所有三个按钮,我会为每个按钮设置密钥吗?我想这将按照您的final zeroKey = GlobalKey&lt;_DataRowState&gt;(); 行进行;我以某种方式给每个 DataRow 一个不同的键?
    • 是的,也可以这样做。然而,另一种方法是提升状态并使用回调函数来做到这一点。
    【解决方案2】:

    另一种方法: 可以通过将状态提升到其父级来实现相同的功能,这对于这种情况来说更方便。

    import 'package:flutter/material.dart';
    
    class Datum extends Object {
      /// data storage object
      double count = 0;
      String word = 'Uninitialized';
      Function? setAndRefresh;
    
      void prt() {
        print('word: $word – count: $count – callback: $setAndRefresh');
      }
    
      void incrementCount() {
        print('incrementCount() -> $count');
        count = count + 1;
        print('                 -> $count');
      }
    }
    
    List<Datum> datums = [];
    
    /// =============================================
    
    void main() {
      // Create three datum in a list
      datums.add(Datum());
      datums.add(Datum());
      datums.add(Datum());
      for (Datum dt in datums) {
        dt.prt();
      }
      datums[0].word = 'All buttons total';
      datums[1].word = 'Button 1 count';
      datums[2].word = 'Button 2 count';
      for (Datum dt in datums) {
        dt.prt();
      }
      runApp(MyApp());
    }
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Testing',
          theme: ThemeData(
            // primarySwatch: Colors.blue,
            backgroundColor: Colors.green,
          ),
          home: Home(),
        );
      }
    }
    
    class Home extends StatefulWidget {
      @override
      State<Home> createState() => HomeState();
    }
    
    class HomeState extends State<Home> {  
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              brightness: Brightness.dark,
              backgroundColor: Colors.green,
            ),
            body: Column(
              // mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                RichText(
                  text: const TextSpan(
                    text: 'Test Buttons',
                  ),
                ),
                ...List.generate(datums.length, (index) => DataRow(
                  currentDatum: datums[index],
                  index: index,
                  onTap: () {
                    setState(() {
                      datums[index].incrementCount();
                      if(index != 0) datums[0].incrementCount();
                    });
                  }
                )),
              ],
            ),
          );
      }
    }
    
    /// =============================================
    
    class DataRow extends StatelessWidget {
      final Datum currentDatum;
      final int index;
      final void Function()? onTap;
    
      const DataRow({Key? key,required this.index,required this.onTap, required this.currentDatum}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return lineItem(
          name: '${currentDatum.word} is now: ${currentDatum.count}  ',
          color: Colors.orange,
        );
      }
    
      Widget lineItem({required String name, required Color color}) {
        return Container(
          width: 500,
          height: 50,
          color: color,
          child: Row(
            children: <Widget>[
              RichText(
                text: TextSpan(text: name),
              ),
              ElevatedButton(
                onPressed: onTap,
                child: Text('Button $index'),
              ),
            ],
          ),
        );
      }
    }
    

    在这种情况下,呈现按钮的小部件被转储,所有数据和操作都由其父级处理。

    但是这种方法仍然存在一些问题,因为正在使用的List&lt;Datum&gt; 是全局的。我们也可以为List&lt;Datum&gt; 创建一个本地范围。

    但对于我们的目的而言,上面的代码可以做到这一点,而无需任何麻烦的按键或任何时髦的逻辑。这也解决了您在更新任何其他小部件时遇到的问题,而无需麻烦。

    然而,缺点是这将更新整个父窗口小部件本身,但这是一个合理的权衡。

    【讨论】:

    • 我会尝试这两种方法,看看我会选择哪种方法。非常感谢!我会尽快报告。
    猜你喜欢
    • 2021-06-08
    • 1970-01-01
    • 2021-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多