Flutter ListView Tutorial – WeightTracker 1

Flutter ListView Tutorial – WeightTracker 1

Creating ListView in Flutter

In this post I am going to go through how I created basic ListView in Flutter with dynamically added values. Let’s get to it.

1. Simple ListView

import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  HomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _HomePageState createState() => new _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<String> strings = new List();

  void _addWeightSave() {
    setState(() {
      strings.add("new string");
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new ListView(
        children:
        strings.map((String string) {
          return new Row(
            children: [
              new Text(string)
            ],
          );
        }).toList(),
      ),

      floatingActionButton: new FloatingActionButton(
        onPressed: _addWeightSave,
        tooltip: 'Add new weight entry',
        child: new Icon(Icons.add),
      ),
    );
  }
}

This code results in simplest list you could imagine.

ListView widget is our main list container (surprising!). It contains many properties but for now we will focus on only one: children.

ListView.children is where we provide our list of Widgets to be displayed. In order to do it we simply map previously initialized list of Strings to list of Rows containing only Text with our value.

In that way we end up with the list view presented on the right.

Now let’s try to make some non-hello-world look.

2. Complex row layout

2.1 Preparing model

class WeightSave {
  DateTime dateTime;
  double weight;

  WeightSave(this.dateTime, this.weight);
}

Simple model for entry used to weight tracking.

2.2 Preparing Layout

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:weight_tracker/model/WeightSave.dart';

class WeightListItem extends StatelessWidget {
  final WeightSave weightSave;
  final double weightDifference;

  WeightListItem(this.weightSave, this.weightDifference);

  @override
  Widget build(BuildContext context) {
    return new Padding(
      padding: new EdgeInsets.all(16.0),
      child: new Row(children: [
        new Expanded(
            child: new Column(children: [
              new Text(
                new DateFormat.yMMMMd().format(weightSave.dateTime),
                textScaleFactor: 0.9,
                textAlign: TextAlign.left,
              ),
              new Text(
                new DateFormat.EEEE().format(weightSave.dateTime),
                textScaleFactor: 0.8,
                textAlign: TextAlign.right,
                style: new TextStyle(
                  color: Colors.grey,
                ),
              ),
            ], crossAxisAlignment: CrossAxisAlignment.start)),
        new Expanded(
            child: new Text(
              weightSave.weight.toString(),
              textScaleFactor: 2.0,
              textAlign: TextAlign.center,
            )),
        new Expanded(
            child: new Text(
              weightDifference.toString(),
              textScaleFactor: 1.6,
              textAlign: TextAlign.right,
            )),
      ]),
    );
  }
}

The layout root is a padding which is basically a wrapper for our layout to make it not touching each other.

A padding’s child is a Row containing 3 children. Rows allows us to stack Widgets horizontally.

To make every child of a Row cover exactly one third of whole space, every element is wrapped into Expanded.

Left column is actually a Column widget. Columns are also tools for stacking multiple widgets but they do it vertically. In Column.children there are two Texts representing exact date and a day of week of our entry.

Middle and right columns are simple Texts. The middle one is designed to represent saved weight value while the right one provides us difference from the previous entry.

2.3 Wrapping it up

To use our new layout we simply replace previous Row with WeightListItem instance (remember to add an import).

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:weight_tracker/WeightListItem.dart';
import 'package:weight_tracker/model/WeightSave.dart';

class HomePage extends StatefulWidget {
  HomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _HomePageState createState() => new _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<WeightSave> weightSaves = new List();

  void _addWeightSave() {
    setState(() {
      weightSaves.add(new WeightSave(
          new DateTime.now(), new Random().nextInt(100).toDouble()));
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new ListView(
        children: weightSaves.map((WeightSave weightSave) {
          //calculating difference
          double difference = weightSaves.first == weightSave
              ? 0.0
              : weightSave.weight -
                  weightSaves[weightSaves.indexOf(weightSave) - 1].weight;
          return new WeightListItem(weightSave, difference);
        }).toList(),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _addWeightSave,
        tooltip: 'Add new weight entry',
        child: new Icon(Icons.add),
      ),
    );
  }
}

And that’s it!

I hope you liked that post and I encourage you to leave a comment with a feedback!

You can find whole project here.

2 thoughts on “Flutter ListView Tutorial – WeightTracker 1

  1. Looks like a good thing to start with and it was explained really well. I would suggest replacing the mapping you used with ListView.builder, like this:

    new ListView.builder(
    itemCount: weightSaves.length,
    itemBuilder: (BuildContext ctx, int index) {
    var save = weightSaves[index];
    var difference = …
    return new WeightListItem(weightSave, difference);
    }
    )

    It might seem like a small difference, but using a builder can increase your app’s performance as the ListView will then figure out what items need to be rebuilt based on whats going to be visible on the screen. For instance, if there are only 6 out of 20 entries visible, the itemBuilder function will only be called 6 times and only 6 WeightListItems are created. However, when using children, all 20 entries will be added as an WeightListItem. Of course, for a simple layout with few children, the difference isn’t noticeable but it is when using many children with complex layouts.

    1. Thanks! Didn’t know about it!
      I found this in the docs: “This constructor is appropriate for list views with a large (or infinite) number of children because the builder is called only for those children that are actually visible.”
      So as you said, in this scenario it won’t be much a difference but if the list grows to much bigger sizes it is something good to know about.
      Thanks again! I will try it soon 🙂

Leave a Reply

Your email address will not be published.