Networking & http in flutter

7 mins

7 mins

Ashutosh

Published on Oct 9, 2024

Networking & http in flutter: Update data over the internet

Learn how to update data over the internet in Flutter. Follow this guide to update user input on a server, handle the response, and display the updated data using Flutter’s http package.
Learn how to update data over the internet in Flutter. Follow this guide to update user input on a server, handle the response, and display the updated data using Flutter’s http package.

Introduction

Updating data over the internet is a common task for mobile apps, especially when dealing with user-generated content or real-time systems. In Flutter, we can use the http package to send HTTP PUT or PATCH requests to update data on a server and handle the response effectively.

In this blog, we’ll cover how to update data from user input, convert the server’s response to a Dart object, and display the updated data on the screen. This guide will provide a complete example of a seamless update operation.

People Also Ask:

  • How do I update data using HTTP in Flutter?

  • How do I send a PUT request in Flutter?

Updating data over the internet is necessary for most apps. The http package has got that covered!

This recipe uses the following steps:

  1. Add the http package.

  2. Update data over the internet using the http package.

  3. Convert the response into a custom Dart object.

  4. Get the data from the internet.

  5. Update the existing title from user input.

  6. Update and display the response on the screen.

Adding the HTTP Package

To begin, you need to include the http package in your Flutter project. This package simplifies making HTTP requests to web services. Open your terminal and run the following command:

flutter pub add http

After adding the package, import it into your Dart file:

import 'package:http/http.dart' as http;

Permissions

For Android, you must also add internet permissions to your AndroidManifest.xml file:

<uses-permission android:name="android.permission.INTERNET" />

For macOS, ensure you include the following in your entitlement files:

<key>com.apple.security.network.client</key>
<true

By adding the http package and the necessary permissions, you set the groundwork for making HTTP requests from your Flutter application.

Updating Data on the Server

In this example, we will use the http.put() method to update an album title on the JSONPlaceholder API, which is a simple fake REST API for testing and prototyping.

Here’s how you can define a function to perform an update:

Future<http.Response> updateAlbum(String title) {
  return http.put(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{
      'title': title,
    }),
  );
}

In this code, we are making a PUT request to update the title of an album with ID 1. The request body contains the new title encoded in JSON format.

The http.put() method returns a Future that contains a Response.

  • Future is a core Dart class for working with async operations. An Future object represents a potential value or error that will be available at some time in the future.

  • The http.Response the class contains the data received from a successful HTTP call.

  • The updateAlbum() method takes an argument, title, which is sent to the server to update the Album.

Converting the Response to a Custom Dart Object

When dealing with HTTP responses, it’s often useful to convert the data into a Dart object for easier handling.

Creating the Album Class

We’ll define a simple Album class to represent the data we will be working with:

class Album {
  final int id;
  final String title;

  const Album({required this.id, required this.title});

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      id: json['id'] as int,
      title: json['title'] as String,
    );
  }
}

Converting the Response to an Album

Next, we will modify the updateAlbum function to return a Future<Album>:

Future<Album> updateAlbum(String title) async {
  final response = await http.put(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{
      'title': title,
    }),
  );

  if (response.statusCode == 200) {
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    throw Exception('Failed to update album.');
  }
}

This modification allows us to handle the response data as an Album object, making it easier to manipulate within our application.

Fetching Data from the Internet

Before updating data, we should first fetch it to ensure we have the latest data displayed in our application. Here’s how to create a function to retrieve an album:

Future<Album> fetchAlbum() async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
  );

  if (response.statusCode == 200) {
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    throw Exception('Failed to load album');
  }
}

The fetchAlbum function sends a GET request to the API and returns an Album object if successful.

Displaying the Updated Data on Screen

To display the data on the screen, we will utilize the FutureBuilder widget. This widget is included with Flutter and simplifies working with asynchronous data sources. You must provide two parameters to the FutureBuilder:

  1. The Future you want to work with. In this case, it will be the future returned from the updateAlbum() function.

  2. A builder function that tells Flutter what to render based on the state of the Future: loading, success, or error.

It's important to note that snapshot.hasData only returns true when the snapshot contains a non-null data value. This is why the updateAlbum function should throw an exception even if the server responds with a "404 Not Found" status. If updateAlbum returns null, the CircularProgressIndicator will display indefinitely.

Here’s how to implement the FutureBuilder:

FutureBuilder<Album>(
  future: _futureAlbum,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text('Updated Title: ${snapshot.data!.title}');
    } else if (snapshot.hasError) {
      return Text('${snapshot.error}');
    }
    return const CircularProgressIndicator();
  },
);

This code snippet checks the state of the future and renders the appropriate UI element based on whether the data is loaded successfully, if an error occurred, or if the data is still loading.

Updating the Existing Title from User Input

To allow users to update the album title, we need to create a user interface with a TextField for input and an ElevatedButton to submit the changes. Here’s how to structure that:

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late Future<Album> _futureAlbum;
  final TextEditingController _controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    _futureAlbum = fetchAlbum();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Update Album')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8),
              child: TextField(
                controller: _controller,
                decoration: const InputDecoration(hintText: 'Enter Title'),
              ),
            ),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _futureAlbum = updateAlbum(_controller.text);
                });
              },
              child: const Text('Update Data'),
            ),
            FutureBuilder<Album>(
              future: _futureAlbum,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  return Text('Updated Title: ${snapshot.data!.title}');
                } else if (snapshot.hasError) {
                  return Text('${snapshot.error}');
                }
                return const CircularProgressIndicator();
              },
            ),
          ],
        ),
      ),
    );
  }
}

In this code, we create a simple UI where users can enter a new title for the album and submit it. The FutureBuilder widget listens for changes to _futureAlbum and updates the displayed title accordingly.

Complete Example

Here’s the complete example code that combines all the previous sections into a single, functional Flutter application:

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

class Album {
  final int id;
  final String title;

  const Album({required this.id, required this.title});

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      id: json['id'] as int,
      title: json['title'] as String,
    );
  }
}

Future<Album> updateAlbum(String title) async {
  final response = await http.put(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{
      'title': title,
    }),
  );

  if (response.statusCode == 200) {
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    throw Exception('Failed to update album.');
  }
}

Future<Album> fetchAlbum() async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
  );

  if (response.statusCode == 200) {
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    throw Exception('Failed to load album');
  }
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late Future<Album> _futureAlbum;
  final TextEditingController _controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    _futureAlbum = fetchAlbum();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Update Album')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8),
              child: TextField(
                controller: _controller,
                decoration: const InputDecoration(hintText: 'Enter Title'),
              ),
            ),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _futureAlbum = updateAlbum(_controller.text);
                });
              },
              child: const Text('Update Data'),
            ),
            FutureBuilder<Album>(
              future: _futureAlbum,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  return Text('Updated Title: ${snapshot.data!.title}');
                } else if (snapshot.hasError) {
                  return Text('${snapshot.error}');
                }
                return const CircularProgressIndicator();
              },
            ),
          ],
        ),
      ),
    );
  }
}

void main() {
  runApp(const MaterialApp(home: MyApp()));
}

Conclusion

In this blog post, we explored how to update data over the internet in Flutter by sending PUT requests to a server. We learned how to fetch existing data, convert it into a Dart object, and utilize the FutureBuilder widget to display the results on the screen. By leveraging these concepts, you can create dynamic applications that provide users with real-time updates and a seamless experience. Happy coding!

Follow us on

Follow us on