Learning Flutter: Asynchronously fetching dog data using Futures

Code and screenshot of app with dogs on the screen.

Over the last six months I have been experimenting with Google’s new mobile development platform, Flutter, on some side projects. I’m totally in love with it and pretty excited to start sharing some of what I have learned. If you’ve hit this page I’ll assume you already know the elevator pitch for Flutter, incase you don’t - here’s the short version.

  • Cross platform (IOS/Android/Potentially more in the future) framework
  • Developed and maintained by Google
  • Comes with great UI components for both Material and Cupertino design systems
  • You write application code in Dart, a modern language also developed by Google, which has a strict type system and familiar syntax to most Object Oriented languages
  • An API and methodology very similar to React (your Flutter ‘Widgets’ even have a setState method that works exactly how you would expect)

Sound good to you? Well, to learn a couple of basic concepts we will be building an app that when opened fetches a bunch of random dog images from the web and renders them in a list of material cards.

// some core UI components
import 'package:flutter/material.dart';

// gives us access to http.get
import 'package:http/http.dart' as http;

// just importing this so I can reference the Response class
// for type reference, not at all essential for functionality
// there would definitely be a more efficient way to do this
// let me know if you have any ideas!
import 'package:http/http.dart';

// gives us json.decode
import 'dart:convert';

// gives us access to Futures and the async keyword
import 'dart:async';

// this is the api we will hit to get a bunch of dog pics
final String dogApiUrl = 'https://dog.ceo/api/breeds/image/random/20';

// Flutter's entry point, whatever runApp returns will be
// rendered to the device's display
void main() => runApp(new MyRandomDogsApp());

// A class to our entire app, no need for state in this example so
// we're going to extend StatelessWidget
class MyRandomDogsApp extends StatelessWidget {

  // Time to fetch some dogs, it is good practice to explicitly label
  // all functions and variables with expected types, here we are defining
  // a function called getDogs which will return a Future (similar to
  // a promise in Javascript) that contains a List (array) of Strings
  Future<List<String>> getDogs() async {

    // a variable named dogs which will contain a List of Strings
    final List<String> dogs = List<String>();

    // here we fetch data from dogApiUrl, this function will wait here
    // until response has been successfully filled with a value
    final Response response = await http.get(dogApiUrl);

    // use json.decode from the dart:convert library to turn a raw json
    // response into a dart Map (object) which expects keys of strings
    // which can contain values of any type (dynamic)
    final Map<String, dynamic> jsonResponse = json.decode(response.body);

    // for this specific dog api, our data lives within 'message' so we're
    // going to loop over that and populate the List that we created earlier
    jsonResponse['message'].forEach((dogImageUrl) => dogs.add(dogImageUrl));

    // and return the data, this will fulfil the Future as complete
    return dogs;
  }

  // Time to render some UI! The build method in Flutter widgets behaves very
  // similarly to the render method on React components, Dart loves to be strict,
  // so we need to explicitly declare that we are overriding this method from
  // the StatelessWidget class that MyRandomDogsApp extends
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Random Dogs',
      home: Scaffold(
        appBar: AppBar(title: Text('Random Dogs Demo')),

        // FutureBuilders is a nice Flutter widget that can dynamically
        // return content based on the snapshot of a Future, in this case,
        // our Future is provided by the getDogs function that we defined above
        body: FutureBuilder(
          future: getDogs(),
          builder: (BuildContext context, AsyncSnapshot snapshot) {

            // here is where we can build UI based on what we have received
            // from getDogs. The below code should be fairly self explanatory,
            // feel free to print(snapshot) in this area and you can take a
            // peek at everything you have access to inside of the snapshot
            switch (snapshot.connectionState) {

              // for all of these cases, just show a spinner
              case ConnectionState.none:
              case ConnectionState.waiting:
              case ConnectionState.active:
                return Center(child: CircularProgressIndicator());

                // if we're complete, show me some dogs 🐕
              case ConnectionState.done:

                // return a ListView.builder, which is basically a fancy
                // more efficient ListView
                return ListView.builder(

                  // for performance reasons, tell the view how many
                  // list items it should expect
                  itemCount: snapshot.data.length,

                  // now the cool part, render out all of your dogs
                  itemBuilder: (BuildContext context, int index) {
                    return Card(
                      child: Image(
                        image: NetworkImage(snapshot.data[index]),
                      ),
                    );
                  },
                );
            }
          },
        ),
      ),
    );
  }
}

The end result should look something like this!

Animation of app demo

Pretty cool right? You should be able to copy and paste the code from above directly into a new application starter and have a play around. If you have an easier time reading code that isn’t littered with my comments you can checkout a clean version on my github here.

Was this pretty cool, or am I barking up the wrong tree? Did I explain something totally wrong? Let me know. We’re all learning together (Flutter is still in beta after all!) so I’m open to any thoughts or feedback.

Until next time, be nice to your neighbors and try to build something fun!

Back to archive