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?
Building the ListView
In the previous section, you implemented the network logic to make a GET request and fetch the list of books. Now, you’ll display those books in a ListView.
Navigate to lib/ui/favorite_book_screen and open favorite_book_screen.dart.
Now, to show the result of the network response, you need to call getBooks() from within FavoriteBooksScreen and wait for the result.
In order to access the getBooks() method you need to create an instance of RemoteDataSource inside _FavoriteBooksScreenState. Replace the first TODO with the following code:
RemoteDataSource _apiResponse = RemoteDataSource();
Next, you need to fetch the list of your favorite books from the backend and display them in a list. To perform this sort of operation Flutter provides a very handy widget named FutureBuilder. You can use that widget to get the task done. Update the second TODO by replaing the current child with the following code:
child: FutureBuilder(
future: _apiResponse.getBooks(),
builder: (BuildContext context, AsyncSnapshot<Result> snapshot) {
if (snapshot.data is SuccessState) {
Library bookCollection = (snapshot.data as SuccessState).value;
return ListView.builder(
itemCount: bookCollection.books.length,
itemBuilder: (context, index) {
return bookListItem(index, bookCollection, context);
});
} else if (snapshot.data is ErrorState) {
String errorMessage = (snapshot.data as ErrorState).msg;
return Text(errorMessage);
} else {
return CircularProgressIndicator();
}
}),
)
Looking over this code, you see that:
- You’ve replaced
TextwithFutureBuilder. As the name suggests, this widget builds itself based on the latest snapshot of interaction with aFuture. -
FutureBuildertakes an instance ofFutureto which it is currently connected. -
AsyncSnapshotholds the result of the HTTP response. Based on the snapshot’s data, it provides an appropriate widget. For example, aCircularProgressIndicatorduring the fetching of data from the server. -
bookListItem(), which you’ll add next, will return aListTilewidget for each book item from the collection. TheseListTilewidgets will be presented in a vertical stack in aListViewwidget.
Now implement bookListItem() to return a ListTile widget containing the details of a book from the collection of favorite books. Add the following code at the bottom of the _FavoriteBooksScreenState class:
ListTile bookListItem(
int index, Library bookCollection, BuildContext context) {
return ListTile(
leading: Image.asset("images/book.png"),
title: Text(bookCollection.books[index].name),
subtitle: Text(
bookCollection.books[index].description,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.caption,
),
isThreeLine: true,
trailing: Text(
bookCollection.books[index].author,
style: Theme.of(context).textTheme.caption,
),
);
}
build, because build could get called every frame. Making 60 calls every second is a worst-case scenario. But for the simplicity of the tutorial, you’re going to break that rule and make the call in build.Breaking down the above implementation:
- The method returns a
ListTilewidget which will hold the details of the book item. - A
ListTilecontains one to three lines of text optionally flanked by icons.
Displaying the Favorite Books List
adb reverse tcp:8888 tcp:8888. The adb version you use must be the one inside your local Android SDK installation, which is often in ~/Android/Sdk/platform-tools. The output from the command should be the port 8888. Build and run the app and see what output you get. If the backend REST app is up and running, you should see the following output:

Making a POST Request
Next, you’ll add the network logic to upload the details of your favorite book by sending the data through a POST request. Go back to lib/network/remote_data_source.dart. In the third TODO, you create a StreamController object:
StreamController<Result> _addBookStream;
Here’s what you’ll be using the StreamController for:
The fourth TODO initializes the StreamController. Add the following code inside init():
-
StreamControllerallows sending data, error and done events on its stream. You’ll use it to send theResultto the UI and to update it accordingly. -
StreamControllerhas two important getters: sink and stream. The sink is of type StreamSink which has a method named add that passes events to the sink. You use stream to get the event that was added to the sink.
The fourth TODO initializes the StreamController. Add the following code inside init():
_addBookStream = StreamController();
Now, you’ll add the logic to make a POST request. Replace the fifth TODO with the following method:
//1
void addBook(Book book) async {
_addBookStream.sink.add(Result<String>.loading("Loading"));
try {
//2
final response = await client.request(
requestType: RequestType.POST, path: "addBook", parameter: book);
if (response.statusCode == 200) {
//3
_addBookStream.sink.add(Result<NetworkResponse>.success(
NetworkResponse.fromRawJson(response.body)));
} else {
_addBookStream.sink.add(Result.error("Something went wrong"));
}
} catch (error) {
_addBookStream.sink.add(Result.error("Something went wrong!"));
}
}
Breaking down this code:
-
addBooktakes aBookas a parameter. -
client.request()makes a POST request to the endpoint,/addBook. You pass thebookas an argument. -
_addStream.sink.add(...)adds the event to theStreamSink. Now,streamcan provide these events to the UI and update it accordingly.
Next, you’ll create a getter method in RemoteDataSource that returns the stream of the StreamController so that the user can see it in the UI. To do this, replace the sixth TODO with the following code:
Stream<Result> hasBookAdded() => _addBookStream.stream;
Since you opened a stream to add events, you must close the stream when you’re done observing the changes. Otherwise, you’ll get unwanted memory leaks.
In dispose(), replace the seventh TODO with the following code:
_addBookStream.close();
Updating the Add Book Screen
Navigate to lib/ui/addscreen and open add_book_screen.dart. The first TODO is to create the RemoteDataSource object. Replace the first TODO with the following code:
RemoteDataSource _apiResponse = RemoteDataSource();
You need to initialize the remote data source in initState() of _AddBookScreenState. Update the second TODO using the following code:
@override
void initState() {
super.initState();
_apiResponse.init();
}
In this code:
-
initState()is a method which is called once when the stateful widget is inserted in the widget tree. - You call
initState()when you add AddBookScreen to the widget tree. You’ll call this method only once, when AddBookScreen is first created.
Next, you have to listen to the stream, exposed by the RemoteDataSource object, for the Result that will be delivered through the sink after the POST request completes.
To do this, replace the third TODO with the following code:
void hasBookAddedListener() {
//1
_apiResponse.hasBookAdded().listen((Result result) {
//2
if (result is LoadingState) {
showProgressDialog();
//3
} else if (result is SuccessState) {
Navigator.pop(context);
Navigator.pop(context);
//4
} else {
SnackBar(
content: Text("Unable to add book"),
duration: Duration(seconds: 2),
);
}
});
}
Breaking down the code:
-
listenadds a subscription to the stream. -
LoadingStatewill show a progress dialog. - In
SuccessState, you’ll navigate back to the “Favorite Book” screen. -
ErrorStatewill show aSnackBarwith the error message.
Update initState to call the method you just added:
@override
void initState() {
super.initState();
_apiResponse.init();
hasBookAddedListener();
}
Finally, you’ll add the logic to submit the book’s detail and make a POST request to upload the details that the user enters.
Replace the fourth TODO with the following code:
final book = Book(
name: _name, author: _author, description: _description);
_apiResponse.addBook(book);
That will collect the details of your book from the TextField and make a POST request.
Congrats! Build and run the app, click the add button, and try adding your favorite book’s details:

If the POST request was successful, you’ll see your book’s details at the end of the list in Favorite Book screen.
