Состояние чтения виджета с отслеживанием состояния - PullRequest
0 голосов
/ 30 апреля 2018

Я медленно создавал приложение с Flutter и изо всех сил стараюсь работать в рамках парадигмы StatefulWidget / State.

Я привык читать или изменять состояние некоторых компонентов пользовательского интерфейса из других классов и не совсем понимаю, как это сделать во Flutter. Например, сейчас я работаю над созданием ListView, который содержит CheckBoxListTiles. Я хотел бы из другого класса пройти и прочитать состояние каждого из флажков в листах списка.

Я чувствую, что это должно быть очень просто на практике, но я не в состоянии придумать средство или пример для эффективного вывода состояния данного виджета таким образом, чтобы с ним можно было работать в другом месте. Я осознаю, что за очевидной трудностью вполне может быть преднамеренная и философская причина, и я сам вижу причины, по которым она будет иметь решающее значение в таких рамках, как Флаттер. Тем не менее, я чувствую себя ограниченным в том, что для новичка не очевидно, как выполнять работу в соответствии с парадигмой.

Как мне узнать значения флажков из класса ItemsList?

Класс товара:

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

}

И список классов:

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

}

1 Ответ

0 голосов
/ 30 апреля 2018

Флаттер отличается от вашего обычного приложения. Он использует принципы, схожие по реакции, которые сами по себе похожи на функциональное программирование

Это означает, что флаттеры имеют ряд ограничений, которые вынуждают вас по-разному проектировать ваше приложение:

  • Виджеты неизменны. Вы не обновляете их. Вы воссоздаете их.
  • Вы не можете получить доступ к состоянию ваших детей. Вы можете получить доступ к данным / состоянию только от своих родителей.

И в качестве награды вы можете быть почти уверены, что любое событие не имеет неявных неизвестных последствий для другого несвязанного класса.


Хорошо, но что это изменит в моем примере?

Это довольно просто: это означает, что невозможно , скажем, для Списка, получить доступ к информации из одного из элементов списка.

Вместо этого вы должны сделать следующее: хранить информацию о всех элементах списка внутри списка. И передай это каждому.

Итак, сначала нам нужно изменить 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
    );
  }
}

Обратите внимание, что все поля неизменны. И что событие 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;
    });
  }
}

Обратите внимание, что вместо того, чтобы поменять список items, я каждый раз создаю новый.

Почему это? Это потому, что, как я объяснил ранее, поля должны быть неизменными. ItemsList может быть с состоянием, но это не причина не следовать этому принципу. Поэтому вместо редактирования списка мы создаем новый на основе старого.

Факт в том, что без этого нового списка , ListView подумал бы: «Эй, ты прислал мне тот же список, что и раньше. Так что мне не нужно обновлять права?».


Самое классное то, что по умолчанию ItemsList содержит всю информацию о ListItem. Так что getSelectedTitles становится довольно легко сделать:

Iterable<String> getSelectedTitles() {
  return items.where((item) => item.isSelected).map((item) => item.title);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...