Making Flutter and REST API Work Together — Preventing Application Freezing (Part 3)

Alex Josef Bigler
Full Struggle Developer
5 min readMar 22, 2023

One of the most critical mistakes any application or graphical interface can make is freezing. We’re not referring to the interface freezing during data loading from the internet or screen rendering, as this is usually resolved within microseconds, and the user may not even notice the loading time. However, there are many factors beyond the developer’s control that can increase loading times to seconds or even tens of seconds. As a result, users may become bored and assume that the application is not working, prompting them to restart, reinstall, or worse, delete the application. What can be done to prevent this issue?

The following posts are suggested reading for this post to get you started:

Creating First Screen

First, let’s create the weather_page.dart screen — a class inherited from StatefulWidget — a class with its own state…blah-blah-blah. If you would like me to write an article about the basic Flutter classes, please let me know in the comments.

It looks like this:

import 'package:flutter_api/api_data_provider.dart';
import 'package:flutter/material.dart';

class WeatherPage extends StatefulWidget {
@override
WeatherPageState createState() => WeatherPageState();
}

class WeatherPageState extends State<WeatherPage> {
final ApiDataProvider apiDataProvider = ApiDataProvider();
late Future<ForecastResponse> response;

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

response = apiDataProvider.getWeather();
}

Important: It is worth noting that the return type of the getWeather() method is a Future. A Future is not an object, but rather a state of an asynchronous operation.

In the main.dart file, we make our screen the home screen:

home: WeatherPage()

We create an instance of a class to work with the API, as well as an instance of an object that will store the result of the API method call.

TL;DR

Note: In Flutter, there is no such thing as multithreading. Everything works on the basis of interruption. This is how the processor works. By performing several operations in parallel, the processor, for example, when data is being written to memory and its participation is not needed, switches to the second operation, and when it is not needed in the second operation, it switches back to the first. Multithreading in modern computers is implemented through multi-core processors, where each core is a separate processor. In Flutter, it is not possible to draw the interface and send a request at the same time, which is why the concept of asynchrony and two magic words, “async” and “await,” emerged. With “async,” we tell the compiler that there will be a breakpoint in this method, and with “await,” we specify where exactly. The general idea is that the invoked method is executed, and as soon as we reach “await,” the method is paused, and we move on to another task, such as drawing the interface. Once the interface is complete, we return to “await,” and if the method near it has executed, we continue executing the originally invoked method.

Therefore, it is necessary to simply handle these states in order to provide the user with the appropriate content at each stage. While the operation is being performed, we display a loading animation, if there is an error, we display an error message, and if the operation is successfully completed, we display the result.

Note: In the article, I will be using the standard loading animation widget. However, nowadays, a more popular approach is to use a skeleton or, more accurately, a shimmer loading animation. This is when the outlines of the final screen are filled with a color that shimmers. We won’t be covering this in detail, but if you’re interested, there’s an official tutorial on how to implement it.

The method call looks like this. Let’s go through it line by line:

  • Line 10 creates an instance of a class to work with the API.
  • Line 11 creates an object that will store the state of an asynchronous API call.

Important: The response object is marked as late. This is a feature of Dart’s null safety implementation. At any given time, the object must have a value, or it must be marked as nullable. The keyword late tells the compiler that we guarantee that the object will have a value by the time the build() method is called. So, during the initialization of our screen’s state in initState() (which is called first in the widget lifecycle), we set a value for the response object.

  • On line 14, we override the initState() method, or more precisely, supplement it with our own logic, by calling the original method beforehand.
  • On line 17, we call the getWeather() method and store its state in the response variable.

Alright

We’ve created the screen and called the method. What’s next? How do we handle the states of the asynchronous operation?

To do this, Flutter provides a special widget called FutureBuilder. It would look something like this:

  • Line 25 adds the FutureBuilder to the widget tree.
  • Line 26 indicates where the state that needs to be monitored is located.
  • On line 28, we create a variable to store whether the data has been loaded or not.

Note: For our current task, it is not necessary, and we could just check the snapshot.hasData flag instead of isReady. However, in the future, if we need to add the ability to request data again, our object will continue to hold the data, and when the asynchronous method completes, it will be updated. But without a trigger to start the loading animation, we will end up with a frozen interface. The connectionState property comes to the rescue, which will be either waiting or active at the moment when the Future is restarted.

  • On line 30, our data has loaded successfully, so we display it on the screen.
  • On line 35, an error occurred, so we display the error message.
  • Line 39 — None of the above conditions were met, so we show the loading indicator.

Here’s what it would look like:

Summary

We can now connect to an API in our application and process simple GET requests without causing the application to freeze. In the next articles, we will explore how to send POST requests, create classes for deserialization based on server swagger documentation, and discuss code reviews.

Subscribe and don’t miss 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.