如何将动态数据从小部件传递到小部件?

时间:2020-06-16 17:19:07

标签: flutter dart

首先让我说起我是Flutter / Dart的新手,而不是超级有经验的程序员。

我试图使自己熟悉flutter的框架和工具,并且试图扩展flutter在项目生成时创建的基本计数器应用程序。我的目标是让应用程序跟踪计数器“重置”的时间,保持计数器的时间和计数,然后在另一个屏幕上的表中显示该数据。

这是我到目前为止的内容: 我开设了一个班来跟踪数据:

class CounterRecord {
  int _counter; //Holds the value the counter was at on reset
  DateTime _resetTime; //Holds the time when the counter was reset

  CounterRecord(int _count){
    _counter = _count;
    _resetTime = DateTime.now();
  }

  int getCount() => _counter; //fetch method for count
  DateTime getTime() => _resetTime; //Fetch method for resettime
}

这是主要的班级/主页:

import 'package:counter_app/clickerScreen.dart';
import 'package:counter_app/dataScreen.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

final clickerKey = new GlobalKey<ClickerScreenState>();

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.deepOrange,
        accentColor: Colors.grey,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  //Enables the passing in of the title, clicker screen instance, and datacreen isntance, respectively,
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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


class _MyHomePageState extends State<MyHomePage> {
  //We don't want a brand new clickerScreen every time, so I'm keeping it up here.
  ClickerScreen clickerScreen = ClickerScreen(clickerKey: clickerKey); //Creates a new clickerScreen - the key points to it too.
  @override
  Widget build(BuildContext context) {
    //Creates an instance (State?) of clickerScreen for the first tab
    return DefaultTabController( //A wrapper that helps manage the tab states
      length: 2, //Currently there are only two options for screens
      child: Scaffold(
        appBar: AppBar( //This represnts the bar up at the top
          title: Text(widget.title),
          bottom: TabBar(
          tabs: [
            //These are the icons for the two tabs we're using
            //The order of these is important: It goes in the same order as TabBarView below
            Tab(icon: Icon(Icons.home)),
            Tab(icon: Icon(Icons.directions_run)),
          ],
          )
        ),
        body: TabBarView(
          children: [
            clickerScreen,
            DataScreen( //this DataScreen will be built every time based on the new data from clickerScreen
              data: clickerKey.currentState.getRecords(),
             ),
          ],
        ),
      ),
    );
  }
}


class CounterRecord {
  int _counter; //Holds the value the counter was at on reset
  DateTime _resetTime; //Holds the time when the counter was reset

  CounterRecord(int _count){
    _counter = _count;
    _resetTime = DateTime.now();
  }

  int getCount() => _counter; //fetch method for count
  DateTime getTime() => _resetTime; //Fetch method for resettime
}

这是我的clickerScreen文件的重要部分:

class ClickerScreen extends StatefulWidget {
  ClickerScreen({Key clickerKey}) : super(key: clickerKey);
  @override
  ClickerScreenState createState(){
    return ClickerScreenState();
  }
}

class ClickerScreenState extends State<ClickerScreen> {

  int _counter = 0;
  List<CounterRecord> records;

/* All three of these functions do very similar things, modify the counter value. */
void _resetCounter(){
    setState(() {
      records.add(CounterRecord(_counter));
      _counter = 0;
    });
  }

List<CounterRecord> getRecords(){
  return records;
}

clickerScreen中有一个构建方法,该方法仅显示按钮和文本。我没有在其中分配密钥,因为它只是返回一个中心小部件,但是我读了一些建议也许应该的东西。

这是我的dataScreen文件:

import 'package:flutter/material.dart';
import 'main.dart';

class DataScreen extends StatefulWidget{
  //Enables the passing in of the instance of the clicker screen instance
  DataScreen({Key key, @required this.data}) : super(key: key);
  final List<CounterRecord> data;

  @override
  _DataScreenState createState(){
    return _DataScreenState();
  }
}
class _DataScreenState extends State<DataScreen>{
  @override
  Widget build(BuildContext context) {
    return Text(widget.data.toString());
  }
}

我知道它的显示实际上并不像它应该的那样,因为我只是将其发送给toString(),但我想确保在开始处理该数据之前可以传递数据。

运行此命令时,在getRecords()上收到NoSuchMethod错误,接收者:null。我也尝试过在ClickerScreen小部件上调用createState(),这是最后一次尝试。

有什么建议吗?

(我将整个clickerScreen文件粘贴在这里(https://pastebin.com/j6Y8M8F3),因为我不想再发布此帖子了。)

2 个答案:

答案 0 :(得分:0)

如果您有两个取决于同一状态的小部件,则必须使用一种称为“提升状态”的东西。这意味着状态是最接近的窗口小部件的一部分,该窗口小部件具有其他两个窗口小部件作为子窗口。在您的情况下,这将是保存CounterRecord List的MyHomePage小部件。它将列表通过构造函数传递给DataScreen,并将onReset回调传递给ClickerScreen。

MyHomePage:

class MyHomePage extends StatefulWidget {
  //Enables the passing in of the title, clicker screen instance, and datacreen isntance, respectively,
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  List<CounterRecord> counterRecord = []; //this is the lifted up state

  onReset(int count) {
    setState(() {
      counterRecord.add(CounterRecord(count));
    });
  }

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      //A wrapper that helps manage the tab states
      length: 2, //Currently there are only two options for screens
      child: Scaffold(
        appBar: AppBar(
            //This represnts the bar up at the top
            title: Text(widget.title),
            bottom: TabBar(
              tabs: [
                //These are the icons for the two tabs we're using
                //The order of these is important: It goes in the same order as TabBarView below
                Tab(icon: Icon(Icons.home)),
                Tab(icon: Icon(Icons.directions_run)),
              ],
            )),
        body: TabBarView(
          children: [
            ClickerScreen(onReset: onReset),
            DataScreen(
              data: counterRecord, //pass the record data to the datascreen
            ),
          ],
        ),
      ),
    );
  }
}

ClickerScreen:

class ClickerScreen extends StatefulWidget {
  final Function(int) onReset;
  ClickerScreen({Key clickerKey, this.onReset}) : super(key: clickerKey);
  @override
  ClickerScreenState createState() {
    return ClickerScreenState();
  }
}

class ClickerScreenState extends State<ClickerScreen> {
  int _counter = 0;

/* All three of these functions do very similar things, modify the counter value. */
  void _resetCounter() {
    widget.onReset(_counter); //call the onReset callback with the counter
    setState(() {
      _counter = 0;
    });
  }

  @override
  Widget build(BuildContext context) {
    ....
  }
}

DataScreen(可以是无状态的,因为状态在其父级中)

class DataScreen extends StatelessWidget{
  //Enables the passing in of the instance of the clicker screen instance
  DataScreen({Key key, @required this.data}) : super(key: key);
  final List<CounterRecord> data;

  @override
  Widget build(BuildContext context) {
    return Text(widget.data.toString());
  }


}

使用这种简单方法会很快变得很烦人,并且在小部件树中移动小部件时需要进行大量更改。这就是为什么存在诸如具有ChangeNotifier或Bloc的Provider之类的高级状态管理的原因。

以下是关于此事的好读物::

https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple

答案 1 :(得分:0)

我听了利奥·莱托(Leo Letto)的建议,而是使用了一个InheritedWidget,该InheritedWidget放置在最上面,其中包含记录列表。