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 Sagar Suri.
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
25 mins
- Getting Started
- Some Important Terminology
- What is a Network Request?
- What is a RESTful API?
- HTTP Methods
- Exploring the Project
- Deploying REST Apps
- Making GET Requests
- Creating Data Model Classes
- Understanding the HTTP Client
- Implementing Network Logic
- Building the ListView
- Displaying the Favorite Books List
- Making a POST Request
- Updating the Add Book Screen
- Making a DELETE Request
- Where to Go From Here?
Creating Data Model Classes
As you can see, the response from the server is in the form of JSON, which Dart cannot interpret directly. So you have to convert the JSON response to something that Dart can understand, i.e parse the JSON and store the details in Dart objects. Back in the starter project, navigate to libs/model/library.dart. You’ll create a data model class that will hold the logic to convert the JSON response to a custom Dart object.
Looking at the JSON response you can see it consists of an array of JSON elements that holds a number of JSON objects. Each JSON object consist of a book detail.
Inside libs/model/library.dart, create a class Book
which will hold the details of a book:
class Book {
final String name;
final String author;
final String description;
Book({this.name, this.author, this.description});
factory Book.fromJson(Map<String, dynamic> json) => Book(
name: json["name"],
author: json["author"],
description: json["description"]);
Map<String, dynamic> toJson() =>
{"name": name, "author": author, "description": description};
}
A Book
holds the properties of a book and provides a custom Dart object. Note that factory
constructors are used to prepare calculated values to forward them as parameters to a normal constructor so that final fields can be initialized with them.
Now in the same file create a class Library
that will convert the JSON to a list of Book
‘s object:
class Library {
final List<Book> books; // 1
Library({this.books});
factory Library.fromRawJson(String str) =>
Library.fromJson(json.decode(str)); // 2
factory Library.fromJson(Map<String, dynamic> json) => Library(
books: List<Book>.from(
json["bookList"].map((x) => Book.fromJson(x))));
Map<String, dynamic> toJson() => {
"bookList": List<dynamic>.from(books.map((x) => x.toJson())),
};
}
Add a necessary import to the top of the file:
import 'dart:convert';
In the Library
code:
-
Library
holds a list ofBook
objects. -
json.decode(jsonString)
converts the JSON string to aMap
object. The key in theMap
object will hold the key of the JSON object, and its value will hold the value of that particular key.
When you make a POST request to add the details of your favorite book, you’ll get the following response:
{ "message": "Book details have been added successfully" }
You need to parse the JSON and convert it to custom Dart object similar to what you did in the previous step.
Navigate to lib/model and open network_reponse.dart. Create a class NetworkResponse
which will parse the JSON and hold the message sent from the server:
import 'dart:convert';
class NetworkResponse {
final String message;
NetworkResponse({this.message});
factory NetworkResponse.fromRawJson(String str) =>
NetworkResponse.fromJson(json.decode(str));
factory NetworkResponse.fromJson(Map<String, dynamic> json) =>
NetworkResponse(message: json["message"]);
Map<String, dynamic> toJson() => {"message": message};
}
Next, you need to understand what an HTTP client is and the importance of using one in the app.
Understanding the HTTP Client
To be able to send a request from your app and get a response from the server in HTTP format, you use an HTTP client. Navigate to lib/network and open book_client.dart. BookClient
is a custom client created specifically to make requests to your book_api backend.
class BookClient {
// 1
static const String _baseUrl = "http://127.0.0.1:8888";
// 2
final Client _client;
BookClient(this._client);
// 3
Future<Response> request({@required RequestType requestType, @required String path, dynamic parameter = Nothing}) async {_
// 4
switch (requestType) {
case RequestType.GET:
return _client.get("$_baseUrl/$path");
case RequestType.POST:
return _client.post("$_baseUrl/$path",
headers: {"Content-Type": "application/json"}, body: json.encode(parameter));
case RequestType.DELETE:
return _client.delete("$_baseUrl/$path");
default:
return throw RequestTypeNotFoundException("The HTTP request method is not found");
}
}
}
In the above implementation:
- All the requests made through this client will go to the baseUrl, i.e. the book_api’s url.
-
BookClient
uses the http.dartClient
internally. AClient
is an interface for HTTP clients that takes care of maintaining persistent connections across multiple requests to the same server. - The
request()
method takes the following parameters:-
RequestType
is an enum class holding the different type of HTTP methods available. -
path
is the endpoint to which the request has to be made. -
parameter
holds the additional information to make a successful HTTP request. For example:body
for a POST request.
-
- The
request()
method executes the proper HTTP request based on therequestType
specified.
You could customize the request()
method by adding more HTTP methods in the switch
statement, e.g. PUT
or PATCH
, based on your requirements.
Next, you’ll implement the network logic to make the GET request and parse the JSON response into Library
.
Implementing Network Logic
Navigate to lib/network and open remote_data_source.dart. This class will hold the logic to make calls to different endpoints like addBook
or deleteBook
and return the result to the upper layer, which can be a view or a repository layer. This type of segregation of data sources into separate classes is part of a layered architecture such as BLoC or Redux.
You have to create an HTTP client that’s responsible for making network requests to a server. Replace the first TODO
with the following line of code:
BookClient client = BookClient(Client());
Client
and BookClient
. Select each one of them and hit option + return on macOS or Alt+Enter on a PC. Select the Import Library option from the dropdown menu.Breaking down the above code:
-
BookClient
abstracts the implementation of HTTP requests from theRemoteDataSource
since it’s only responsibility is to hold the business logic. -
Client()
creates anIOClient
ifdart:io
is available and aBrowserClient
ifdart:html
is available, otherwise it will throw an unsupported error.
Replace the second TODO
with the following code:
//1
Future<Result> getBooks() async {
try {
//2
final response = await client.request(requestType: RequestType.GET,
path: "books");
if (response.statusCode == 200) {
//3
return Result<Library>.success(Library.fromRawJson(response.body));
} else {
return Result.error("Book list not available");
}
} catch (error) {
return Result.error("Something went wrong!");
}
}
Use the same hot-key as above for your platform to import any needed files.
Breaking down the code above:
- The return type of the method is
Future
. A future represents the result of an asynchronous operation, and can have two states: completed or uncompleted. -
client.request(requestType: RequestType.GET, path: "books")
will make a GET request to the/books
endpoint with an asynchronous call using the keywordawait
. -
Result
is a generic class which has three subclasses:LoadingState
,SuccessState
andErrorState
.