Flutter Apps

12.1 API Calls and Async

Module Still Under Development

# Futures

Futures are part of Dart:core. They looks a lot like JS Promises. The three states that a Future object can be in are: uncompleted, completed with data, or completed with error.

There are a number of other classes that we use that return Futures. HTTP requests return a Future. SharedPreferences.getInstance returns a Future. We will be talking more about SharedPreferences next week when talking about Data Persistence.

Futures in Dart

Here is an example that shows a Future created by an HTTP Request.

ElevatedButton(
  onPressed: () {
    final myFuture = http.get('https://example.com/some/image.jpg');
    myFuture.then( (resp) {
      _myHandleResponse(resp);
    }).catchError( (err) {
      print('Caught $err');
    }).whenComplete( () {
      //just like finally in JS
      //runs after either then() or catchError()
    })
  },
  child: Text('Click to load')
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Here is an example of a generic Future, think JS Promise.

import 'dart:async'; //important to remember to import this
// dart:async gives us `Future`

void main() {
  final myFuture = Future(() {
    //code here will run second
    return 'hello world';
  });

  //code here will run first
}
1
2
3
4
5
6
7
8
9
10
11

A Future.value() is like the JavaScript Promise.resolve().

A Future.error(Exception()) is like the JavaScript Promise.reject(new Error()).

A then().catchError( (err){ }) is like the JavaScript then().catch( function(err){ }).

One of the common things we do in JS is to wrap a setTimeout in a Promise to use as a function that delays something in our code from running. In Dart, a Future has a method called delayed which does exactly this. The example below will run the Duration Widget for 5 seconds and then call the second parameter function as if it were Future.value(12).

final myFuture = Future<int>.delayed(
  Duration(seconds: 5),
  () => 12,
)
//notice the comma after Duration(), not a semi-colon
1
2
3
4
5

If you want, you can chain a .then() on to the end of that call to Future.delayed. If you want to handle errors on your Future (and you always should), then chain a .catchError((err) { }) after the .then(). Unlike JavaScript Promises, you can add a .catchError after each .then.

At the end of your chain of then and catchError functions you can add a .whenComplete() to the end of the chain. It will always run after either the last .then or .catchError.

Future reference

# Async / Await

async await in Dart

In Dart the async and await works like async / await in JavaScript. You can use a series of await wrapped commands instead of chained then methods.

# JSON encode and decode

In our Flutter apps, to work with JSON being sent and received from an API, we need to import the (data:convert library](https://api.flutter.dev/flutter/dart-convert/dart-convert-library.html). When writing a Flutter app, we automatically have access to anything in the dart:core library but other dart functionality and classes need to be imported.

The dart:convert library includes lots of methods for converting between different formats of strings. We will be focused on the JsonDecoder and JsonEncoder classes.

JsonDecoder class reference

JsonEncoder class reference

//Simple HttpHelper class for making http requests
import 'dart:convert';
//gives us jsonDecode and jsonEncode
import 'dart:async';
//gives us Future
import 'package:http/http.dart' as http;
//we can give names to the packages we import

class HttpHelper {
  //skipping over any variable declarations for domain, path, params

  Future<Map> getData(String keyword) async {
    //we could create our own custom object in the Future

    Uri uri = Uri.https(domain, path, params); //Uri in dart:core
    http.Response response = await http.get(uri); //http get request
    Map<String, dynamic> data = jsonDecode(response.body);
    //convert the json String in the response body
    // into a Map object with String keys.
    return data;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Here is the Dart Guide to working with JSON. It talks about how to serialize and deserialize JSON.

# IDE, API, Library, Framework, and SDK

Ever wonder what the difference is between these five things - IDE, API, Library, Framework, and SDK?

While there are different interpretations with different types of programming and disagreements between developers about the finer points, here is a general reference to what each is:

IDE is an Integrated Development Environment. This is a text editor on steroids. It has features that help developers write and compile their code, as well as, manage all their projects. Usually, they have integrations with Source Control (Git) too.

API is an Application Programming Interface. This is a group of functions or server-side endpoints that let another program access a service or data-source.

Library is one or more files that use the same programming language as your project. They can be included in your project to provide pre-tested code that will speed up your development work.

Framework is similar to a library but will typically also include UI components, design assets, best practice guidance, and conventions to follow.

SDK is a software development kit. It tends to be the largest of all of these. It will often include a library or framework. Most importantly, it will include tools that you need in order to develop for your target platforms, such as a compiler or testing tools.

# Http Package

The http package comes from the pub.dev site. Here is the link to the http package and here is the API documentation for http.

Dart core has a Uri class for creating or parsing URLs from Strings.

To make a single httprequest from your app you can use the http.post, http.get, http.delete, http.head, http.patch, or http.put methods.

If you want to make a series of requests then you can use an async await function with an http.Client() object. Create the client and use the client as a wrapper for all the get and post calls. Use await in front of each of the calls to wait for the response before moving to the next one.

# Alternative

Here is the Dart guide to working with HTTP Requests. This guide talks about using the HttpRequest package that is part of Dart, instead of the http package that we imported from pub.dev.

# Custom Data Model Classes

When you make a fetch call in a web app we use JSON.parse() to turn the JSON string into a plain JavaScript object. In the dart example above we were passing the result of the JSON parsing into a simple Map like this: Map<String, dynamic> data = json.decode(response.body);.

Consider that the bare bones approach.

In languages that are more strongly typed, it is considered a best practice to pass that Map into a custom class object that is specifically designed for our data. We can filter out properties that we don't want, we can do validation on the data, we can provide default values.

This process is similar to what we did in our PWA when we made a call to TMDB api and then called the Array.map method to only keep the properties that we wanted to save in the IndexedDB.

Applying this idea in dart we will create a class and create a variable for each of the properties we want and assign them the proper data types.

Create a folder inside /lib called data. This is where we will put our HttpHelper class as well as our custom data object classes.

// /lib/data/movie.dart

class Movie {
  int id;
  String title;
  String overview;
  DateTime? release_date; //nullable
  String? poster_path; //nullable because null is allowed here
  double popularity;

  Movie(this.id, this.title, this.overview, this.release_date, this.poster_path, this.popularity);
}
1
2
3
4
5
6
7
8
9
10
11
12

Those six variables that we declared will all be available as instance properties of the Movie object. The constructor function will accept a generic Map object and it will destructure the various named property values from the Map based on the String keys in the Map.

So, this would work but we have no default values or validation. To solve that we are going to add a new named constructor method inside our Movie class. All constructors will create an instance of your class. You are allowed to have ONE unnamed constructor but as many unnamed constructors as you want.

Movie.fromJson(Map<String, dynamic> dataMap){
  id = dataMap['id'] ?? 0;
  title = dataMap['title'] ?? '';
  overview = dataMap['overview'] ?? '';
  if(dataMap['release_date'] == null){
    release_date = DateTime.tryParse(dataMap['release_date']);
  }else{
    release_date = null;
  };
  poster_path = dataMap['poster_path'] ?? 'default_image.jpg';
  popularity = dataMap['popularity'] ?? 0;
  // return Movie(id: id,
  //  title: title,
  //  overview: overview,
  //  release_date: release_date,
  //  poster_path: poster_path,
  //  popularity: popularity);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

This way we are accepting the full map of data that comes from the API and we are going to assign only the values we want from the Map to our instance variables. We are also using the nullish coalescing operator to check if any of them are null. If they are then we are assigning a default value. We could do more data validation or custom errors here too.

The return statement cannot be used if it is a basic named constructor. Constructors are not allowed to have return statements. However, we can add one and actually use that return statement that calls the default constructor by adding the keyword factory in front of the Movie.fromJson(

# Working with DateTime

Reference for the core DateTime class.

# HttpHelper

The final step is to build our own helper class to use with api calls. For each API that we are going to use we could create a separate helper class. Inside each helper class we could create a new function for each endpoint that we want to use.

// /lib/data/http_helper.dart

import './movie.dart'; //our movie data object class
import 'package:http/http.dart' as http;  //to make http calls
import 'dart:async'; //for Future
import 'dart:convert'; //for JSON to Map conversion

class HttpHelper {
  final String domain = 'example.com';
  final String path = 'api/v2/endpoint';
  final String apiKey = 'YourApiKeyForThisApi';

  Future<Movie> searchMovies(String keyword) async {
    //these are the querystring params
    Map<String, dynamic> params = {
      'keyword': keyword,
      'lang': 'en',
      'apikey': apiKey
    };
    //create the URL for the endpoint with parameters
    //we are assuming that our API call is returning one object
    Uri uri = Uri.https(domain, path, params);
    //make the fetch call to the api
    http.Response result = await http.get(uri);
    //convert the json string response into a Map
    List<dynamic> data = jsonDecode(response.body) as List<dynamic>;
    //pass the Map to the Movie.fromJson method for validation and default values
    //and removal of unwanted properties
    Movie movie = Movie.fromJson(data);
    //return the movie object that we got from the API
    return movie;
  }

  //add other functions to handle other endpoints
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# Flutter Cookbook

In the Flutter.dev website cookbook section they have a simple example of using the http package and a custom data class to fetch and use JSON data from an online API. Flutter fetch cookbook recipe.

# What to do this week

TODO Things to do before next week

  • Work on Flutter App 2
  • Read all the notes for modules 12.1, 12.2, 13.1
Last Updated: 11/20/2023, 9:45:24 AM