Flutter Networking Tutorial: Getting Started
In this tutorial, you’ll learn how to make asynchronous network requests and handle the responses in a Flutter app connected to a REST API. By Karol Wrótniak.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Flutter Networking Tutorial: Getting Started
30 mins
- Getting Started
- Exploring the Project
- Defining Important Terms
- Understanding Network Requests and Responses
- Understanding RESTful APIs
- Understanding Endpoints
- Understanding HTTP Methods
- Understanding HTTP Status Codes
- Understanding JSON
- Discovering Retrofit
- Understanding Asynchronous Programming
- Running the REST API Server
- Preparing for Development
- Adding Dependencies
- Generating Data Model Classes
- Implementing the REST Client Skeleton
- Performing GET Request
- Performing DELETE Request
- Performing POST and PUT Requests
- Where to Go From Here?
Understanding JSON
The data sent over the internet is a set of bytes or characters. Assume that you want to send the title and the author of some book. There is no information on which position each property starts and ends. You have to send some metadata to be able to interpret the response. That is where you can use JSON (JavaScript Object Notation).
It’s a language-independent data format readable by humans. It looks like this:
{
"author":"John Doe",
"title":"Mike \"The cursed\"",
"rating":4.5,
"tags":[
"history",
"thriller"
]
}
In the snippet above you have a list of key-value pairs (also called fields). For example, the author
key points to John Doe
. It’s the same kind of structure as Map
in Dart.
The tags
field is an ordered list of values, just like a List
in Dart. JSON supports the following types of fields:
- string – enclosed in double quotes,
- number – can hold an
int
ordouble
type, - null,
- boolean –
true
orfalse
, - nested object containing any of the types from the previous list,
- array of elements of any type from this list.
Notice the standardized notation. There are double quotes around all the strings and the square brackets enclosing an array. Line breaks and indentation are only for human readability purposes. In the real data transferred over the internet servers usually omit them to reduce data size.
That’s only the tip of an iceberg. The JSON specification covers other topics you may want to read about, learn more in the official documentation.
Discovering Retrofit
Retrofit is a type-safe dio client generator. You can use it to generate code that makes requests to various API endpoints through HTTP methods using dio under the hood. You only have to define the API interface using annotations. Retrofit along with the retrofit_generator
package generates all the boilerplate, low-level code for you.
Look at the snippet below:
@POST('/items') // 1
Future<void> createItem(
@Body() String item, // 2
@Header('Content-Type') String contentType, // 3
);
In the code above you have annotations defining the following properties:
- an HTTP method and an endpoint,
- the payload type, may also be e.g.
@Field
or@Part
, - the HTTP header.
Implementation of that method would contain dozens of lines of code instead of five if you used dio directly. So you can save a significant amount of time when using retrofit in comparison to writing that code manually. Moreover, the generated code is less error-prone. For example it won’t forget to pass some header to a dio instance.
Understanding Asynchronous Programming
The last theoretical part you’ll learn here is asynchronous programming. First, take a look at these two simple functions:
void main() {
sayHello();
print('Hello from main');
}
void sayHello() {
print('Hello from function');
}
Run main
, and you’ll see the following the output:
flutter: Hello from function
flutter: Hello from main
The order of the print
statements in the code and the printed messages in the output are the same. Thus, the functions are synchronous.
Now, look at the following asynchronous code:
void main() {
sayHello();
print('Hello from main');
}
Future<void> sayHello() async { // 1
await Future.delayed(const Duration(seconds: 1)); // 2
print('Hello from function');
}
And its output:
flutter: Hello from main
flutter: Hello from function
Now, the lines are in reversed order. Hello from function
appears one second later. In the code above, you have:
- The async keyword — an indicator that the function is asynchronous.
- await indicating that the function waits until the Future completes.
Note the function return type. It’s not void
but Future<void>
. Future holds the results of some asynchronous computation. It may be a success or a failure if the called method throws an error.
Finally, change main
to the following and run it:
Future<void> main() async {
await sayHello();
print('Hello from main');
}
The function is asynchronous but it waits for sayHello
to complete. The output is the same as in the first snippet.
You can also wait for the future to complete without using await
. Take a look at this snippet:
void downloadBook() {
Future.delayed(const Duration(seconds: 1)) // 1
.then((_) => getBookFromNetwork()) // 2
.then((_) => print('Books downloaded successfully')) // 3
.catchError(print); // 4
}
Future<void> getBookFromNetwork() async { // 5
// Get book from network
}
This code:
- Delays for one second.
- Then, calls
getBookFromNetwork()
after the delay completes. - Either prints the success message after
getBookFromNetwork()
completes successfully. - Or prints the error message if
getBookFromNetwork()
throws an error. - Provides a
getBookFromNetwork()
stub that downloads the data from the internet (body omitted).
To sum up:
- If you want to execute something without blocking the caller, mark the function async. Use
Future
return type then. - Use await to wait until the
Future
or async function call completes.
Now you know some theory, it’s time to move on to running the REST API server.
Running the REST API Server
The backend project uses the conduit framework. It’s an HTTP web server framework for building REST applications written in Dart. You won’t get into how to write server-side code using Dart. You’ll just deploy this project and focus on implementing the networking logic in the app.
To start making HTTP requests to the server, you need to deploy it on your machine. Before you begin, ensure you have access to the dart
executable. Open the terminal and invoke the following command:
dart --version
You’ll get something like this as a result:
Dart SDK version: 3.0.5 (stable)
If dart isn’t accessible, you’ll get an error (Command 'dart' not found"
or similar). Follow the Update your path instructions for your platform: Windows, macOS or Linux to fix the issue.
Next, run:
-
dart pub global activate conduit
in your terminal. -
conduit serve
in your terminal from theapi
directory.
Those commands may take a few seconds to produce some results. After a while, you should see something like the following in your terminal:
-- Conduit CLI Version: 4.4.0
-- Conduit project version: 4.4.0
-- Starting application 'api/api'
Channel: ApiChannel
Config: /Users/koral/projects/bookshelf/api/config.yaml
Port: 8888
[INFO] conduit: Server conduit/1 started.
This command never stops in case of success. As long as the server is running, you won’t be able to use the same terminal window/tab. The client’s requests will appear in the log. You can press Control-C to stop the server.
The base URL for the server is http://localhost:8888
. There isn’t any database here. It stores all the data in memory. So, if you stop and start the server again, you’ll lose all modifications.