Update data over the internet
Updating data over the internet is necessary for most apps. The http package has got that covered!
This recipe uses the following steps:
-
Add the
httppackage. -
Update data over the internet using the
httppackage. - Convert the response into a custom Dart object.
- Get the data from the internet.
-
Update the existing
titlefrom user input. - Update and display the response on screen.
1. Add the http package
To install the http package, add it to the dependencies section of the pubspec.yaml file. You can find the latest version of the http package on pub.dev.
dependencies:
http: <latest_version>
Import the http package.
import 'package:http/http.dart' as http;
2. Updating data over the internet using the http package
This recipe covers how to update an album title to the JSONPlaceholder using the http.put() method.
Future<http.Response> updateAlbum(String title) {
return http.put(
'https://jsonplaceholder.typicode.com/albums/1',
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'title': title,
}),
);
}
The http.put() method returns a Future that contains a Response.
-
Futureis a core Dart class for working with async operations. AFutureobject represents a potential value or error that will be available at some time in the future. -
The
http.Responseclass contains the data received from a successful http call. -
The
updateAlbum()method takes an argument,title, which is sent to the server to update theAlbum.
3. Convert the http.Response to a custom Dart object
While it’s easy to make a network request, working with a raw Future<http.Response> isn’t very convenient. To make your life easier, convert the http.Response into a Dart object.
Create an Album class
First, create an Album class that contains the data from the network request. It includes a factory constructor that creates an Album from JSON.
Converting JSON by hand is only one option. For more information, see the full article on JSON and serialization.
class Album {
final int id;
final String title;
Album({this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
id: json['id'],
title: json['title'],
);
}
}
Convert the http.Response to an Album
Now, use the following steps to update the updateAlbum() function to return a Future<Album>:
-
Convert the response body into a JSON
Mapwith thedart:convertpackage. -
If the server returns an
UPDATEDresponse with a status code of 200, then convert the JSONMapinto anAlbumusing thefromJson()factory method. -
If the server doesn’t return an
UPDATEDresponse with a status code of 200, then throw an exception. (Even in the case of a “404 Not Found” server response, throw an exception. Do not returnnull. This is important when examining the data insnapshot, as shown below.)
Future<Album> updateAlbum(String title) async {
final http.Response response = await http.put(
'https://jsonplaceholder.typicode.com/albums',
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'title': title,
}),
);
if (response.statusCode == 200) {
// If the server did return a 200 UPDATED response,
// then parse the JSON.
return Album.fromJson(json.decode(response.body));
} else {
// If the server did not return a 200 UPDATED response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
Hooray! Now you’ve got a function that updates the title of an album.
4. Get the data from the internet
Get the data from internet before you can update it. For a complete example, see the Fetch data recipe.
Future<Album> fetchAlbum() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/albums/1');
if (response.statusCode == 200) {
// If the server did return a 200 OK response, then parse the JSON.
return Album.fromJson(json.decode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
Ideally, you will use this method to set _futureAlbum during initState to fetch the data from the internet.
5. Update the existing title from user input
Create a TextField to enter a title and a RaisedButton to update the data on server. Also define a TextEditingController to read the user input from a TextField.
When the RaisedButton is pressed, the _futureAlbum is set to the value returned by updateAlbum() method.
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _controller,
decoration: InputDecoration(hintText: 'Enter Title'),
),
),
RaisedButton(
child: Text('Update Data'),
onPressed: () {
setState(() {
_futureAlbum = updateAlbum(_controller.text);
});
},
),
],
)
On pressing the Update Data button, a network request sends the data in the TextField to the server as a POST request. The _futureAlbum variable is used in the next step.
5. Display the response on screen
To display the data on screen, use the FutureBuilder widget. The FutureBuilder widget comes with Flutter and makes it easy to work with async data sources. You must provide two parameters:
-
The
Futureyou want to work with. In this case, the future returned from theupdateAlbum()function. -
A
builderfunction that tells Flutter what to render, depending on the state of theFuture: loading, success, or error.
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 in the case of a “404 Not Found” server response. If updateAlbum returns null then CircularProgressIndicator will display indefinitely.
FutureBuilder<Album>(
future: _futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.title);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
)
Complete example
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/albums/1');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(json.decode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
Future<Album> updateAlbum(String title) async {
final http.Response response = await http.put(
'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) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(json.decode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to update album.');
}
}
class Album {
final int id;
final String title;
Album({this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
id: json['id'],
title: json['title'],
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
@override
_MyAppState createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
final TextEditingController _controller = TextEditingController();
Future<Album> _futureAlbum;
@override
void initState() {
super.initState();
_futureAlbum = fetchAlbum();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Update Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Update Data Example'),
),
body: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(8.0),
child: FutureBuilder<Album>(
future: _futureAlbum,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(snapshot.data.title),
TextField(
controller: _controller,
decoration: InputDecoration(hintText: 'Enter Title'),
),
RaisedButton(
child: Text('Update Data'),
onPressed: () {
setState(() {
_futureAlbum = updateAlbum(_controller.text);
});
},
),
],
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
}
return CircularProgressIndicator();
},
),
),
),
);
}
}
Semantic portal