Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading State of a Stateful Widget

Tags:

flutter

dart

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

}

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

}
like image 933
jared-nelsen Avatar asked Apr 30 '18 01:04

jared-nelsen


People also ask

What is state in stateful widget?

A stateful widget is a widget that describes part of the user interface by building a constellation of other widgets that describe the user interface more concretely.

What is state in stateful widget Flutter?

Stateful Widgets: The widgets whose state can be altered once they are built are called stateful Widgets. These states are mutable and can be changed multiple times in their lifetime. This simply means the state of an app can change multiple times with different sets of variables, inputs, data.

What is state of a widget?

State is information that (1) can be read synchronously when the widget is built and (2) might change during the lifetime of the widget. It is the responsibility of the widget implementer to ensure that the State is promptly notified when such state changes, using State.

What is the lifecycle of stateful widget?

The lifecycle of a stateful Widget explains the calling hierarchy of functions and changes in the state of the widget. Everything in the flutter is a widget. Basically, the Stateful and stateless are the types of widget in the flutter.


1 Answers

Flutter is different from your usual app. It uses principles similar to react, which themselves are similar to functional programming.

Which means flutters comes with a set of restrictions that forces you to architecture your app differently :

  • Widgets are immutable. You don't update them. You recreate them.
  • You can't access the state of your children. You can only access data/state from your parents.

And as reward, you can be almost certain that any event don't have implicit unknown consequences on another unrelated class.


Okey, but what does this change to my example ?

That's pretty straightforward : It means that it's impossible for say a List, to access informations from one of the list item.

What you should do instead is the opposite : Store the information of all list items inside the list. And pass it down to each.

So first we need to change ListItem so that it gets everything from it's parent. We can also make it stateless because it doesn't store any information anymore.

With these, ListItem suddenly becomes pretty straightforward :

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

Notice that all fields are immutable. And that the onChange event passed to CheckboxListTile is passed as parameter too !

At this point you may think "But isn't that class pointless now" ? Not really. In our case the layout is dead simple. But this is a good practice to create such classes, as it splits layout. Cleaning the code of the parent for layout logic.

Anyway let's continue.

Now, let's modify the parent so that it contains informations and pass it down :

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

Notice here how instead of mutating the items list, I create a new one everytimes.

Why is it ? That is because, like I explained before, fields are supposed to be immutable. ItemsList may be stateful, but it's not a reason to not follow that principle. So instead of editing the list, we create a new one based on the old one.

Fact is that without that new list, ListView would think "Hey, you sent me the same list as before. So I don't need to refresh right ?".


The cool thing now, is that by default ItemsList has all the informations about ListItem. So getSelectedTitles becomes pretty easy to do :

Iterable<String> getSelectedTitles() {
  return items.where((item) => item.isSelected).map((item) => item.title);
}
like image 137
Rémi Rousselet Avatar answered Dec 28 '22 01:12

Rémi Rousselet