Josh
Josh

Reputation: 2444

Widget Architecture for Flutter App

I am making this app, but am unsure of a good way to organize it. With the picture to demonstrate below:

enter image description here

Where (currently):

What the app needs to do: The digits need to update themselves whenever they change (I am not worried about fancy animations right now, just transitioning).

Problem: I found that the only way to update these digits is through Timer.periodic every minute. However, I am finding it hard to route this information. I currently have my periodic timer under each Red Expanded Widget, but there is a unique DateTime used in all of them. Redundant information is the problem with this approach. I could place a periodic timer in the Blue ListView, Rows Widget. However, every update would require a reload of all containing widgets, including the Green Expanded Widgets.

If there are any suggestions or different ways, anything would be appreciated.

Upvotes: 0

Views: 360

Answers (1)

Richard Heap
Richard Heap

Reputation: 51770

Flutter's design means that it doesn't matter that you reload the green Widgets; the way it works makes that really inexpensive. So, drag your state up to a StatefulWidget containing all your text/columns/etc (which can now be stateless).

This example shows one way of pushing down a fragment of state (into the constructor of DurationPartWidget). I've shown that you could do the date mathematics in the build method. Equally, you could do it in setState and make years, months, etc instance variables of _AnniversaryWidgetState.

class AnniversaryWidget extends StatefulWidget {
  final DateTime firstDate;

  AnniversaryWidget(this.firstDate);

  @override
  State createState() {
    return new _AnniversaryWidgetState();
  }
}

class _AnniversaryWidgetState extends State<AnniversaryWidget> {
  Timer _timer;

  @override
  void initState() {
    super.initState();
    _timer = new Timer.periodic(new Duration(seconds: 1), (Timer t) {
      setState(() {});
    });
  }

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

  @override
  Widget build(BuildContext context) {
    DateTime now = new DateTime.now();
    // todo - calculate these from now minus firstDate
    int years = 0;
    int months = 4;
    int days = 13;
    int hours = 21;
    int minutes = now.difference(widget.firstDate).inMinutes % 60;
    return new Center(
      child: new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          new _DurationPartWidget('years', years),
          new _DurationPartWidget('months', months),
          new _DurationPartWidget('days', days),
          new _DurationPartWidget('hours', hours),
          new _DurationPartWidget('minutes', minutes),
        ],
      ),
    );
  }
}

class _DurationPartWidget extends StatelessWidget {
  final int _numberPart;
  final String _label;

  _DurationPartWidget(this._label, this._numberPart);

  @override
  Widget build(BuildContext context) {
    return new Row(
      children: <Widget>[
        new Text(_numberPart.toString().padLeft(2, '0')),
        new Text(_label),
      ],
    );
  }
}

If, later, you want to bring you state even higher up your Widget tree, you could use an InheritedWidget. (I sometimes use one above MaterialApp.) Brian Egan gave an awesome talk at DartConf 2018 on this whole topic. https://www.youtube.com/watch?v=zKXz3pUkw9A&list=PLOU2XLYxmsIIJr3vjxggY7yGcGO7i9BK5&index=10

Upvotes: 1

Related Questions