Making HTTP calls in Flutter — A Comprehensive Beginner Guide.

Making HTTP calls in Flutter — A Comprehensive Beginner Guide.

·

7 min read

Photo by Omar Flores on Unsplash

Being able to receive and send information over the web is an integral part of most mobile applications. This post aims to help you take the first baby steps in understand ‘The Flutter Way’ of making api calls.

To do so, we are going to create a mobile application to retrieve a list of messages.

Understanding HTTP

HTTP is the protocol (or set of rules) that allow us to transfer data across the web. HTTP uses a server-client model. In simple terms, this means when you attempt to retrieve messages on your mobile device, you act as a client, making a request to GET the information. The server — a host running web server software — is tasked with returning an appropriate response to your request. If successful, you can then use the data however you wish! If you fail, you die.

Dart’s http package that makes this easy. To use this in your Flutter application, you must first include the following *http dependency in the pubspec* file of your application. The pub spec file allows you to specify what your project depends on (imports, fonts, images, third parties libraries etc).

dependencies:
  flutter:
    sdk: flutter
  http: 0.12.1

Now we move on to making the request.

Making a GET request.

For the sake of simplicity, our GET request in Flutter will follows these sequential steps:

But to best understanding how it fits together, we will take an Inside-Out approach.

When working with an API, one of the first things to do is to look at the data it spits out — either through documentation or testing it out. This allows us to understand the format of the result, if it contains what we want, and to develop a mental map of how to work with it.

Looking at the data

For this tutorial, we make a GET request using this url “*https://jsonplaceholder.typicode.com/comments*”.

{
  "postId": 1,
  "id": 2,
  "name": "quo vero reiciendis velit similique earum",
  "email": "Jayne_Kuhic@sydney.com",
  "body": "est natus enim nihil est dolore omnis voluptatem numquam\net omnis occaecati quod ullam at\nvoluptatem error expedita pariatur\nnihil sint nostrum voluptatem reiciendis et"
},

Above is a snippet of the expected body of a successful response. We can see structure of our message, what it contains, as well as the specific formatting of the JSON data — a collection of keys and values. However, we know the response body is a string. So we must ask ourselves the following: How do we use this in our application? Must we convert this into another object so dart understands?

Your questions will be answered. But for now, since we are aware of what the result of our message request entails, we can device a model for our messages.

Creating our model.

This model is a way for us to define what we deem useful from the response we get from the server and map it a form that can be used across our application. In this tutorial, we do not care about the “postId” or “id” shown in the JSON string above, we only care for the name, email and body of each comment.

class MessageModel {
  String _body;
  String _name;
  String _email;

  MessageModel.fromJson(Map<String, dynamic> json) {
    _name = json['name'];
    _email = json['email'];
    _body = json['body'];
  }

  get body => _body;

  get email => _email;

  get name => _name;
}

The MessageModel is an immutable object that contain only getter methods to access its data (like in lines 12 to 16). It’s also worth mentioning that the underscore (‘_’ ) before the name of identifiers means the object is private to its library.

Lines six to ten is where the magic happens. As previously mentioned, there is an extra step that takes the JSON string and decodes it so that Dart understands. Line six assumes this step has occurred and the string body has been transformed into a Map. We are more concerned with taking this Map of keys and values and transforming it into our MessageModel.

It is convention to write a method called ‘fromJson()’ to generate this instance of the MessageModel. It’s worth pointing out that we have a type Map<String, dynamic> because even though the key is always a string, it may be possible for the value to be of any type i.e an integer. Hence, the keyword ‘dynamic’.

In line seven to nine, each property out of the MessageModel extracts the value of the JSON (in our case: name, email and body), and stores the values that are only retrievable by getters.

Making the Get request.

Now that we have devised a model to extract what bits of information we want from our response, we can make the request.


class MessageProvider {
  final String _url = "https://jsonplaceholder.typicode.com/comments";
  Client client = Client();

  Future<List<MessageModel>> fetchPosts() async {
    final response = await client.get(_url);
    if (response.statusCode == 200) {
        final parsed = json.decode(response.body);
        return parsed.map<MessageModel>((json) => MessageModel.fromJson(json)).toList();
    } else {
      throw Exception('Failed to load messages');
    }
  }
}

Let’s take it line by line.

On line four, we create an instance of the Client to allow us to make our request. We then define a fetchPost() method that makes the call and returns a list of our MessageModel. Why a list? Well, remember that the api call returns a list of comments in the body of our response.

On line 7, we make the call and get a response that we store in the response variable. For this to occur, we have the async modifier that allows us to run this code asynchronously. The await keyword let us run this code and only move on to the next line when it’s done.

After we retrieve the response, we check the status code for a 200. A http 200 indicates a success and that everything went ok!

Line 9 states that if everything is fine, we should get the body of the response and decode it. What do we mean by decode? Remember, we mentioned that when we get our response body, we must first convert the string into a JSON string — a map with string keys, so that Dart understands it. Line 9 does exactly that and stores the result with the ‘parsed’ identifier.

Now, we are left with a list containing dart objects that hold the contents of our messages. On line 14, we loop through the contents of this list and creates a MessageModel containing the data we care about.

Remember this MessageModel method? This is how we generate a MessageModel from our map.

MessageModel.fromJson(Map<String, dynamic> json) {
    _name = json['name'];
    _email = json['email'];
    _body = json['body'];
}

Now we end up with a list of MessageModels holding the information we need.

We have data. How do we use it?

For the sake of simplicity, we will assume architecture design is not at the forefront of the agenda. We know how to get the data and we just want to show it! The code below shows this. It might seem overwhelming, so we’ll go bit by bit.

class MessagePage extends StatefulWidget {
  @override
  _MessagePageState createState() => _MessagePageState();
}

class _MessagePageState extends State<MessagePage> {

  bool isLoading = false;
  List<MessageModel> messageList;
  MessageProvider messageProvider = new MessageProvider();

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

  Future _fetchData() async {
    setState(() => isLoading = true);
    messageList = await messageProvider.fetchPosts();
    setState(() => isLoading = false);
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          backgroundColor: Colors.pinkAccent,
          title: Center(child: Text('Messages', style: TextStyle(color: Colors.black),),),
        ),
        body: Center(
            child: isLoading
                ? CircularProgressIndicator()
                : ListView.builder(
              itemCount: messageList.length,
              itemBuilder: (BuildContext context, int index) {
                return ListTile(
                    contentPadding: EdgeInsets.symmetric(
                        horizontal: 10.0,
                        vertical: 10.0
                    ),
                    title: Text(messageList[index].email, style: TextStyle(fontSize: 20),),
                    subtitle: Text(messageList[index].body)
                );
              },
            )
        )
    );
  }
}

Firstly, remember we created a MessageProvider class that allows us to make our API call. In our _fetchData method, we call this method to get our data.

But, what about the waiting period before we receive the data?

To account for this, we hope to use a CircularProgressIndicator() (that spinning circle loading thingy we are all familiar with) to show our content is loading.

On line 19, we set ‘isLoading’ to true. We then retrieve our data before setting isLoading back to false to hide the spinner. All this is done in the initState() method after the object is created, before Flutter paints it on the screen. We then use a ListView.builder to populate our screen, displaying the email and body of each email.

Et Voilà. All done!

Next we will dive deeper into how we can separate layers with the BLoC (Business Logic Component) architectural pattern. Till then, au revoir!