Reading State of a Stateful Widget

chr*_*788 5 dart flutter

I've been slowly building an app with Flutter and am struggling to work under the StatefulWidget/State paradigm.

I am used to being able to read or alter the state of some UI component from other classes and do not quite understand how to do this in Flutter. For example, right now I am working to build a ListView that contains CheckBoxListTiles. I would like to, from another class, go through and read the state of each of the checkboxes in the list tiles.

I feel like this should be very straightforward in practice but I an unable to come up with a means or an example to effectively externalize the state of a given Widget such that it can be worked with elsewhere. I am aware that there may very well be an intentional and philosophical reason behind the apparent difficulty and can myself see reasons why it would be critical in a framework such as Flutter. However, I feel restricted in that it is not obvious to a newcomer how to do work under the paradigm off the bat.

How can I go about reading the values of the checkboxes from the ItemsList class?

The Item class:

class ListItem extends StatefulWidget {

  String _title = "";
  bool _isSelected = false;

  ListItem(this._title);

  @override
  State<StatefulWidget> createState() => new ListItemState(_title);
}

class ListItemState extends State<ListItem> {

  String _title = "";
  bool _isSelected = false;

  ListItemState(this._title);

  @override
  Widget build(BuildContext context) {

    return new CheckboxListTile(

      title: new Text(_title),
      value: _isSelected,
      onChanged: (bool value) {
        setState(() {
          _isSelected = value;
        });
      },

    );

  }

  String getTitle() {
    return _title;
  }

  bool isSelected() {
    return _isSelected;
  }

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

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

}
Run Code Online (Sandbox Code Playgroud)

And the list class:

class ItemsList extends StatefulWidget {

  @override
  State<StatefulWidget> createState() => new ItemsListState();

}

class ItemsListState extends State<ItemsList> {

  List<ListItem> _items = new List();

  @override
  Widget build(BuildContext context) {

    return new Scaffold(

      body: new ListView(
        children: _items.map((ListItem item) {
          return item;
        }).toList(),
      ),

      floatingActionButton: new FloatingActionButton(
        onPressed: addItem,
        child: new Icon(Icons.add),),

    );
  }

  void addItem() {
    setState(() {
      _items.add(new ListItem("Item"));
    });
  }

  List<String> getSelectedTitles() {
    List<String> selected = new List();

        ***This is where I would like to externalize the state***

    for(ListItem e in _items) {
      if(e.isSelected()) {
        selected.add(e.getTitle());
      }
    }

    return selected;
  }

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

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

}
Run Code Online (Sandbox Code Playgroud)

Rém*_*let 10

Flutter与您通常的应用不同。它使用相似的原理来做出反应,它们本身类似于函数式编程。

这意味着抖动带有一系列限制,迫使您以不同的方式构建应用程序:

  • 小部件是不可变的。您不更新它们。您重新创建它们。
  • 您无法访问孩子的状态。您只能从父母那里访问数据/状态。

作为奖励,您几乎可以确定,任何事件都不会对另一个不相关的类产生隐式的未知后果。


Okey,但这对我的示例有什么变化?

这非常简单:这意味着说一个List 不可能从其中一个列表项中访问信息。

相反,您应该做的相反:将所有列表项的信息存储在列表中。并将其传递给每个人。

因此,首先我们需要进行更改ListItem,以使其从父级获取所有信息。我们也可以使其成为无状态的,因为它不再存储任何信息。

有了这些,ListItem突然变得很简单:

class ListItem extends StatelessWidget {
  final String title;
  final bool isSelected;
  final ValueChanged<bool> onChange;

  ListItem({ @required this.onChange, this.title = "", this.isSelected = false, });

  @override
  Widget build(BuildContext context) {
    return new CheckboxListTile(
      title: new Text(title),
      value: isSelected,
      onChanged: onChange
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

请注意,所有字段都是不可变的。并且onChange传递给的事件CheckboxListTile也作为参数传递!

在这一点上,您可能会认为“但现在这堂课不是没有意义吗?” 并不是的。在我们的情况下,布局非常简单。但这是创建此类的好习惯,因为它会拆分布局。清理父代码的布局逻辑。

无论如何,让我们继续。

现在,让我们修改父对象,使其包含信息并将其传递给:

class ItemsList extends StatefulWidget {
  State<StatefulWidget> createState() => new ItemsListState();
}

class ItemsListState extends State<ItemsList> {
  List<ListItem> items = new List<ListItem>();

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new ListView(
        children: items,
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: addItem,
        child: new Icon(Icons.add),
      ),
    );
  }

  void addItem() {
    final listItemIndex = items.length;
    final newList = new List<ListItem>.from(items)
      ..add(
        new ListItem(
          onChange: (checked) => onListItemChange(listItemIndex, checked),
          title: "Item n' $listItemIndex",
          isSelected: false,
        ),
      );

    this.setState(() {
      items = newList;
    });
  }

  onListItemChange(int listItemIndex, bool checked) {
    final newList = new List<ListItem>.from(items);
    final currentItem = items[listItemIndex];
    newList[listItemIndex] = new ListItem(
      onChange: currentItem.onChange,
      isSelected: checked,
      title: currentItem.title,
    );

    this.setState(() {
      items = newList;
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

请注意,在这里我不是items每次更改列表,而是每次都创建一个新列表。

为什么 ?这是因为,正如我之前解释的那样,字段应该是不可变的。ItemsList可能是有状态的,但这不是不遵循该原则的理由。因此,我们无需编辑列表,而是在旧列表的基础上创建一个新列表。

事实是,如果没有该新列表ListView就会想到“嘿,您给我发送的列表与以前相同。所以我不需要刷新吗?”。


现在最酷的事情是,默认情况下ItemsList具有有关的所有信息ListItem。因此getSelectedTitles变得很容易做到:

Iterable<String> getSelectedTitles() {
  return items.where((item) => item.isSelected).map((item) => item.title);
}
Run Code Online (Sandbox Code Playgroud)