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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Performing DELETE Request

Next, you’ll implement the DELETE request method to delete a book from the list. To do so, open lib/ui/bookshelf_screen.dart and replace // TODO: implement DELETE network call with the following snippet:

          _dataSource
              .deleteBook(books[index].id!) // 1
              .then((_) =>
              _bookStreamController.add(books..removeAt(index))) // 2
              .catchError((_) {
            ScaffoldMessenger.of(context).showSnackBar( // 3
                const SnackBar(content: Text('Could not delete the book')));
          });

In the code above:

  1. You made a DELETE request, passing the ID of the book to delete.
  2. If it succeeds, you remove the book from the local copy and refresh the stream controller.
  3. You show a snackbar in case of error.

Note that updating the book list in the stream controller causes the StreamBuilder widget to rebuild. The framework calls its builder method with a new snapshot value.

Notice also that the dismissed item disappears from the UI whether the request succeeds or not. But, the Dismissible widget of the removed item may still be in the tree. This will cause a fatal error if you refresh the list by tapping the refresh icon button.

Next, similar to the GET request, open lib/network/rest_client.dart and add the following method:

  @DELETE('/books/{id}')
  Future<void> deleteBook(@Path('id') int id);

Unlike the GET request, the DELETE takes the book ID as a parameter. The name inside the curly braces — {id} — in @DELETE annotation has to match the one declared in @Path. The name of the Dart method parameter is by convention the same.

Don’t forget to regenerate the code using build_runner.

Finally, go to lib/network/data_source.dart. Find the // TODO: implement DELETE request and replace the method with this code:

Future<void> deleteBook(int id) => _restClient.deleteBook(id);

Here, you just delegate the work to the REST client without any extra logic.

Now, reveal the terminal window where you’re running conduit. Then, launch the app and swipe some item to dismiss it. Here’s what you’ll see on the device:
DELETE request in action
And, this is what you’ll see in the terminal:

[INFO] conduit: GET /books 33ms 200   
[INFO] conduit: DELETE /books/1 8ms 204  

Note the HTTP status codes at the end of the lines.

Performing POST and PUT Requests

The POST and PUT requests share most of the code. The only difference is the subject of PUT has an ID, but the ID is null in case of POST. Tap the floating action button (+) to create a new book. This action will send the POST request. You can also edit a book by tapping it on the list. That’ll send the PUT request.

Open lib/ui/add_or_update_book_screen.dart and replace // TODO: implement network call with the following fragment:

          await widget.dataSource
              .createOrUpdateBook(book) // 1
              .then((_) => Navigator.pop(context, true)) // 2
              .catchError((_) => ScaffoldMessenger.of(context).showSnackBar(// 3
                  const SnackBar(
                      content: Text('Could not create or update the book'))));

Breaking down the code above:

  1. Makes the POST or PUT request call.
  2. Navigating back to the previous screen in case of success.
  3. Displaying a snackbar in case of success.

The true value of pop call doesn’t have any meaning. You need anything other than null.

Next, go to lib/network/rest_client.dart and add the actual POST and PUT requests code:

  @POST('/books')
  Future<void> createBook(@Body() Book book);

  @PUT('/books/{id}')
  Future<void> updateBook(@Path('id') int id, @Body() Book book);

Notice the @Body annotation on the book parameters. It places the data inside the HTTP request body. There is an ID inside the book object, yet, the backend API takes an ID as a path element, analogous to the GET request.

As usual, regenerate the code:

dart run build_runner build --delete-conflicting-outputs

The distinction between PUT and POST happens in lib/network/data_source.dart. Locate // TODO: implement POST and PUT requests there and replace it with:

    if (book.id == null) {
      await _restClient.createBook(book); // 1
    } else {
      await _restClient.updateBook(book.id!, book); // 2
    }

In the code above, you have:

  1. The POST request call.
  2. The PUT request call.

Finally, launch the app and try to add a new book by tapping the floating action button with a + icon. You’ll see an effect like this:
PUT request in action

Edit a book by tapping a book:
POST request in action

Congratualtions. You have successfully completed this article! You should now be able to make network requests using dio and retrofit.

Where to Go From Here?

Download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.

To learn more about Flutter networking using the http package and a different approach, take a look at the official documentation:

Or, explore more about the following topics:

We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!