A Flutter App that populates a list via HTTP

In a few of my previous articles on Flutter, I have touched on how to access the web via HTTP.  But I thought it would be a good exercise to dive into HTTP a bit further by building an app.  In this article I’ll build a simple stock tracker app using HTTP to get the latest prices for a list of stocks you can customize.

NOTE: If you don’t really care to see how we built the app and want to jump ahead to see the HTTP portion of this post, click here.  Otherwise onward!

Creating the app

If you are not yet familiar with the basics of creating a flutter app, please see my first post in this series on flutter – From Zero to App with Flutter.

Note – for this post we’ll be using VS Code although other IDEs may be used.  Open up VS Code and from the command palette (CTRL+SHIFT+P) choose ‘Flutter:New Project’.  Enter a new project name and hit Enter.  VS Code will setup your new project, which we will be heavily modifying next…

A high level design overview of the stock watcher app

We’ll build a simple user interface – essentially a list of stocks and a modal dialog that allows you to add to the list.  Here is what it will look like:

In order to achieve the above, we will for the most part rely on 3 main widgets:

  1. A ListView to store the list of stocks.
  2. An AlertDialog to present a simple text input to the user.
  3. A FloatingActionButton to invoke the dialog.

Let’s build the shell of this – starting with the ListView.  We’ll break this code out into a separate file so create a new file named stock_list.dart under your lib folder and place the following code within it:

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'stock.dart';

class StockList extends StatefulWidget {

  StockList({Key key, this.stocks}) : super(key: key);

  final List<Stock> stocks;

  @override
  State<StatefulWidget> createState() {
    return new _StockListState();
  }
}

class _StockListState extends State<StockList> {

  @override
  Widget build(BuildContext context) {
    return _buildStockList(context, widget.stocks);
  }

  ListView _buildStockList(context, List<Stock> stocks) {
    return new ListView.builder(
      itemCount: stocks.length,
      itemBuilder: (context, index) {
        return ListTile(title: Text('${stocks[index].symbol}'));
      },
    );
  }

}

Let’s quickly review the above.  The constructor takes in a list of stocks to display in the list.  As you can see, we will have to create a new class to represent a stock.  We’ll get to that code in a moment.  Moving on down class, the real meat of this is in the ListView.builder method.  This method takes the list of stocks and for each item in the list, creates a new ListTile to show the stock.  It’s rather bare bones for now but we can improve this once we get the app functioning properly.

Now, it’s time to create a model class named Stock.  Place the following code in a new file (under lib again) called stock.dart:

class Stock {
  String symbol;
  double price;

  Stock(this.symbol, this.price);
}

At this point we have a new widget that can display a list of stocks that are supplied to it.  Let’s hook it into our application and see if we can get it to display.  Open up main.dart and add the following imports at the top of the file:

import 'stock_list.dart';
import 'stock.dart';

There are also a few things to clean up here.  Remove the code for the default app that was created for you and change the title to ‘Stock Watcher App’ (or whatever you want it to be).  When you are done, main.dart should look like the following:

import 'dart:async';
import 'package:flutter/material.dart';
import 'stock_list.dart';
import 'stock.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Stock Watcher',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Stock Watcher App'),
    );
  }
}

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

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

class _MyHomePageState extends State<MyHomePage> {

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

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Container(
        child: new Center(          
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: null,
        tooltip: 'Add',
        child: new Icon(Icons.add),
      ),
    );
  }
}

At this point if you run the app, you will see a screen similar to the following:

But clicking on the ‘+’ button (floating action button) does nothing!  Let’s address that…

Adding an item to our ListView

Let’s make it so that when a user clicks on the floating action button, we invoke a dialog that allows the user to input a stock ticker symbol (e.g. GOOGL).

First off, lets specify a method to invoke when onPressed is called.  Locate the floatingActionButton in main.dart and implement the onPressed handler as follows:

floatingActionButton: new FloatingActionButton(
        onPressed: () => _inputStock(),
        tooltip: 'Add',
        child: new Icon(Icons.add),
      ),

Now, when the button is pressed, a method named _inputStock will be invoked.  Here is the implementation for _inputStock:

Future<Null> _inputStock() async {
    await showDialog<String>(
        context: context,
        builder: (BuildContext context) {
          return new AlertDialog(
            title: const Text('Input Stock Ticker Symbol'),
            contentPadding: EdgeInsets.all(5.0),
            content: new TextField(
              decoration: new InputDecoration(hintText: "Ticker Symbol"),
              onChanged: (String value) {
                _model = value;
              },
            ),
            actions: <Widget>[
              new FlatButton(
                child: new Text("Ok"),
                onPressed: () {
                  setState(() {
                    if (_model.isNotEmpty) {
                      _stockList.add(new Stock(_model, 0.00));
                    }
                    _model = "";
                  });
                  Navigator.pop(context);
                },
              ),
              new FlatButton(
                child: new Text("Cancel"),
                onPressed: () => Navigator.pop(context),
              ),
            ],
          );
        });
  }

The code for _inputStock is rather lengthy, so let’s review.  First off, the method is asynchronous.  If you are not familiar with async / await, check out my article on async / await in dart. The showDialog method returns a Future so we have to use the keyword await before it and decorate _inputStock with the async keyword.  Let’s turn our attention now to the meat of this method – the code within the builder.

Our builder code puts together an AlertDialog that contains three items: a TextField for entering a stock ticker symbol, and two buttons (Ok and Cancel).  All of this is rather straight-forward, but the code that actually takes what the user entered and puts it into the list is worth a little more attention.  What we do is track when the user changes the TextField by hooking into the onChanged event and then store the value in the _model property (which we have not added yet).  Then, when the user presses the Ok button, we take what is in the _model property and add it to our list of stocks.  Let’s add in those two properties now –

Just below the _MyHomePageState class declaration, add in the following code:

var _stockList = new List<Stock>();
  String _model = "";

Great!  So all that is left now is to add in our StockList widget that we created earlier.  Within the _MyHomePageState class, locate the build method and update the body as follows:

body: new Container(
        child: new Center(
          child: new StockList(stocks: _stockList),
        ),
      ),

Ok, let’s run it and give it a test.  It should function like the below.

 

 

If it does not – check your code.  For your convenience, the full source code to this point is here on GitHub.

Obtaining prices for our stocks via HTTP

To facilitate the retrieval of stock prices, we will depend on the free API offered by iextrading.com.  The API we will use grabs a price for the stock based on its ticker symbol (read more on the specifics of this API here).  In order to do so we will create a new class that will be responsible for interacting with the API and retrieving our price.  Create a new file under lib and call it stock_service.dart.  Here is the code for our new service:

import 'package:http/http.dart' as http;
import 'dart:async';

class StockService {

  Future<double> getPrice(String symbol) async {
    String url = "https://api.iextrading.com/1.0/stock/${symbol}/price";

    http.Response response = await http.get(url);
    double price = double.tryParse(response.body);
    return price;
  }

}

Next, let’s import that service into main.dart by adding the following line to the top of the file:

import 'stock_service.dart';

A little lower down in the same file, within _MyHomePageState class, create an instance of the stock service as follows:

StockService _stockService = new StockService();

Next, lets refactor the onPressed event of the Ok button so that we can call the stock service’s getPrice method.  Here is the updated onPressed code:

onPressed: () async {
  if(_model.isNotEmpty) {
    double price = await _stockService.getPrice(_model);
    setState(() {
      _stockList.add(new Stock(_model, price));
    });
  }                  
  _model = "";                  
  Navigator.pop(context);
}

Now, if we run the app, when we add a stock, we will see a price displayed directly below the ticker symbol:

But what if a price can’t be obtained?  Let’s take care of that by a slight refactor stock_list.dart.  Locate the code that constructs the ListTile and replace it with the following snippet:

return ListTile(title: Text('${s.symbol}'), subtitle: Text('${s.price ?? "price not found"}'));

Summary

In this article we built a simple app that shows you how to add items to a list and grab some of the data for that list from an HTTP service.  As you can see, working with HTTP is really easy to do.  There are however lots of things we could do to improve this app, such as:

  1. Removing an item from our list
  2. Persisting the list
  3. Pull to refresh

If the interest is there, I may dig into the above and perhaps add improvements in future articles.  Leave a comment if you want to see this happen!

Here is a link to the full source code up to this point: stock watcher app.

Disclaimers:

Data provided for free by IEX. View IEX’s Terms of Use.

2 thoughts on “A Flutter App that populates a list via HTTP”

Leave a Reply

Your email address will not be published. Required fields are marked *