Making Flutter and REST API Work Together —Creating API Provider (Part 1)

Alex Josef Bigler
Full Struggle Developer
6 min readMar 18, 2023

Why is a backend or API necessary for application development?

From a user’s perspective, it’s important to ensure that data isn’t lost when the application is deleted or to synchronize information across different devices.

From a developer’s perspective, implementing as much functionality as possible on the backend is preferable since it makes maintaining and debugging the application easier. If the functionality is on the server, updating doesn’t require rebuilding and reinstalling the application on all devices. Additionally, debugging errors on the server is simpler because each user may have unique devices, whereas everything on the server is the same.

Another reason for using a backend is due to the limited resources of user’s devices, particularly for mobile devices. Processing large amounts of data may strain these devices, so it’s better to perform these tasks on the server and only send necessary data to the device. Overall, using a backend and API simplifies development, improves performance, and helps save user data.

Integrating an application with an API is not a difficult task. Firstly, it’s necessary to choose a data exchange protocol, which is typically HTTP. Then, defining the message format for transmitting data between the application and server is important. The most common format used is JSON, as it enables data transmission in a readable format and is widely supported by most server and client technologies. However, other formats such as binary or Google’s protobuf can be used, which may be more efficient in certain cases.

In this article, we will stick to the basics and avoid using complex libraries. We will integrate with the Open-Meteo API, which provides weather information, for free. This will enable us to implement support for the most common data types and receive up-to-date weather information. It’s worth noting that we won’t need an API key for this service.

Let’s get started.

Adding API provider

To begin, let’s create a new Flutter project and name it flutter_api. After that, we need to add the http package version 0.13.1 in the pubspec.yaml file. We can do this by adding the following line:

dependencies:
http: ^0.13.1

Next, we’ll create a new file in the lib folder called api_data_provider.dart. In this file, we’ll define a class that will provide methods for fetching data from our API.

We’ll start by creating a method called getWeather() that retrieves weather data using a GET request to the API. To accomplish this, we’ll use the http class from the http package:

The code creates a class named ApiDataProvider, which contains a method called getWeather() that makes an HTTP request to the Open-Meteo API to retrieve the weather forecast for the specified coordinates.

  • The first line imports the http package, which enables making HTTP requests.
  • On line 3, the ApiDataProvider class is declared, which will be used to retrieve data from the API.
  • The getWeather() method (line 4) is asynchronous, indicated by the Future keyword. It returns a String object containing the API response.
  • Line 5 introduces a 2-second delay to simulate a long request.
  • Lines 7–8 create a Client object from the http library, which is used to make the HTTP request.
  • On line 10, a Content-Type header is added to the request, specifying that we expect a JSON response.
  • On line 12, a GET request is formed using the get() method from the http library, with the Open-Meteo API URL and request parameters: latitude, longitude, current_weather, and hourly.
  • Line 19 checks the server response status. If the status is 200, the response body containing the weather forecast in JSON format is returned. If the response status is not 200, an exception is thrown, reporting the error.

In this scenario, the response is just a string, which is inconvenient for further processing. Hence, it’s necessary to deserialize the string into an object. We can infer the response format from the API description page:

{
"latitude": 52.52,
"longitude": 13.419,
"elevation": 44.812,
"generationtime_ms": 2.2119,
"utc_offset_seconds": 0,
"timezone": "Europe/Berlin",
"timezone_abbreviation": "CEST",
"hourly": {
"time": ["2022-07-01T00:00", "2022-07-01T01:00", "2022-07-01T02:00", ...],
"temperature_2m": [13, 12.7, 12.7, 12.5, 12.5, 12.8, 13, 12.9, 13.3, ...]
},
"hourly_units": {
"temperature_2m": "°C"
},
"current_weather": {
"time": "2022-07-01T09:00",
"temperature": 13.3,
"weathercode": 3,
"windspeed": 10.3,
"winddirection": 262
}
}

To properly handle the response from the Open-Meteo API, we need to create a class that describes its structure. This will allow us to easily extract and manipulate the relevant information.

TL;DR

I want to warn hot-headed individuals against using simple solutions and code generation services. For example, the popular https://app.quicktype.io is mentioned in almost every other Flutter tutorial.

Pause this article, copy the JSON response example above into this service, and see the result, which will be something like:

Now compare these two code snippets below and think about when this:

class Hourly {
Hourly({
required this.time,
});

final List<String> time;

factory Hourly.fromJson(Map<String, dynamic> json) => Hourly(
time: List<String>.from(json["time"].map((x) => x)),
);

Map<String, dynamic> toJson() => {
"time": List<dynamic>.from(time.map((x) => x)),
};
}

became better than this:

class ForecastResponseHourly {
List<DateTime> time = [];

ForecastResponseHourly();

factory ForecastResponseHourly.fromJson(Map<String, dynamic> json) {
var r = ForecastResponseHourly();

for (int i = 0; i < json['time'].length; i++) {
r.time.add(DateTime.parse(json['time'][i]));
}

return r;
}
}

Here are some hints:

  • The second code snippet uses more explicit names for the class properties, making it easier to understand what they represent without having to look at their types or default values.
  • The second code snippet doesn’t use .map() iterator, because .map() slower then while/for loop in Dart.
  • The second code snippet more clearly defines the types of properties, making it easier to read the code and prevent typing errors.
  • The second code snippet contains less code, making it easier to read and understand.
  • If you write code like the second snippet, you are unlikely to be out of work, and your job won’t be replaced by an AI (it’s a joke).

Сreating a class that describes the structure of the API response

Instead of describing every field of the response, we will focus on a few examples to illustrate the serialization of parameters, nested objects, and arrays.

In this case, we will create three classes that represent the JSON response from the Open-Meteo API and describe its structure:

The following three classes are created to represent the JSON response from the Open-Meteo API and describe its structure:

  • ForecastResponse: contains data about the location’s latitude and longitude, current weather (currentWeather), and hourly weather forecast (hourly). Both currentWeather and hourly fields are nested objects.
  • ForecastResponseCurrentWeather: contains information about the current weather, and its only field is temperature.
  • ForecastResponseHourly: contains a list of dates and times of the forecast for the next few hours. When deserializing the JSON string from the server response, all dates and times in the time array are iterated over and added to the ForecastResponseHourly.time list.

It is important to note that the factory method takes Map<String, dynamic> as input, but the response body is mentioned as a string in the above code. To convert the string into a dictionary, the jsonDecode method from the built-in dart:convert library is used. To create an instance of the ForecastResponse class, the fromJson method is called with the dictionary passed to it.

To modify the getWeather() method to return an object of the ForecastResponse class, its implementation needs to be slightly modified.

Summary

The above code demonstrates how to retrieve and deserialize a server response using the GET method in Dart. While this example focused on the GET method, other request methods such as POST, PUT, and DELETE are also available for interacting with servers. For instance, the POST method allows sending a deserialized JSON object to the server. These methods and their specific use cases warrant a separate article.

Furthermore, the code could be improved by abstracting the logic of forming requests into a separate class and processing server requests without freezing the interface.

Future articles will cover these topics, so be sure to subscribe to stay up to date on new materials.

--

--

Alex Josef Bigler
Full Struggle Developer

Enthusiast of new technologies, entrepreneur, and researcher. Writing about IT, economics, and other stuff. Exploring the world through the lens of data.