Closed petermichaux closed 4 years ago
Good idea.
@sfshaza2 are pull requests welcome?
You betcha!!! We don't always suggest it, but they are always welcome.
Verify that the mock API can do what's necessary for the full set of GET/POST/PUT/DELETE recipes without complicating them with unrelated details. It looks like it can.
(I don't see anything for incremental paged loading of a list of resources. It would be nice for an infinite scroll example.)
Read/GET all resources
$ curl -i https://jsonplaceholder.typicode.com/albums
HTTP/2 200
date: Mon, 10 Feb 2020 22:46:47 GMT
content-type: application/json; charset=utf-8
set-cookie: __cfduid=da95146d177867edae84c53042ca814901581374807; expires=Wed, 11-Mar-20 22:46:47 GMT; path=/; domain=.typicode.com; HttpOnly; SameSite=Lax
x-powered-by: Express
vary: Origin, Accept-Encoding
access-control-allow-credentials: true
cache-control: max-age=14400
pragma: no-cache
expires: -1
x-content-type-options: nosniff
etag: W/"2475-YUx9CiwTgoHXeL4210gtmqFIZNA"
via: 1.1 vegur
cf-cache-status: MISS
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 56319f00e9cef4a6-YVR
[
{
"userId": 1,
"id": 1,
"title": "quidem molestiae enim"
},
{
"userId": 1,
"id": 2,
"title": "sunt qui excepturi placeat culpa"
},
{
"userId": 1,
"id": 3,
"title": "omnis laborum odio"
},
{
"userId": 1,
"id": 4,
"title": "non esse culpa molestiae omnis sed optio"
},
...
]
Read/GET a resource
$ curl -i https://jsonplaceholder.typicode.com/albums/1
HTTP/2 200
date: Mon, 10 Feb 2020 22:45:51 GMT
content-type: application/json; charset=utf-8
content-length: 64
set-cookie: __cfduid=d525317651095b8708feec80c00b5fc821581374751; expires=Wed, 11-Mar-20 22:45:51 GMT; path=/; domain=.typicode.com; HttpOnly; SameSite=Lax
x-powered-by: Express
vary: Origin, Accept-Encoding
access-control-allow-credentials: true
cache-control: max-age=14400
pragma: no-cache
expires: -1
x-content-type-options: nosniff
etag: W/"40-74G1+b66MteeTYAz6G+NybtDGFA"
via: 1.1 vegur
cf-cache-status: MISS
accept-ranges: bytes
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 56319da508c43af4-YVR
{
"userId": 1,
"id": 1,
"title": "quidem molestiae enim"
}
Create/POST a resource. Verifying we can pretend that the userId
parameter doesn't exist so that the example code doesn't involve concepts of authentication and a session user.
$ curl -i \
> --header 'Content-Type: application/json; charset=UTF-8' \
> --data '{"title":"Super Cool Mix Tape"}' \
> https://jsonplaceholder.typicode.com/albums
HTTP/2 201
date: Mon, 10 Feb 2020 22:58:23 GMT
content-type: application/json; charset=utf-8
content-length: 49
set-cookie: __cfduid=d8676a357807df96ad27eeba4b1c1ca8a1581375503; expires=Wed, 11-Mar-20 22:58:23 GMT; path=/; domain=.typicode.com; HttpOnly; SameSite=Lax
x-powered-by: Express
vary: Origin, X-HTTP-Method-Override, Accept-Encoding
access-control-allow-credentials: true
cache-control: no-cache
pragma: no-cache
expires: -1
access-control-expose-headers: Location
location: http://jsonplaceholder.typicode.com/albums/101
x-content-type-options: nosniff
etag: W/"31-stY/FnssNHUwY6wFqYq40cdZB4o"
via: 1.1 vegur
cf-cache-status: DYNAMIC
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 5631affedba6f4ba-YVR
{
"title": "Super Cool Mix Tape",
"id": 101
}
Update/PUT a resource. Again, verifying userId
isn't required by the server API.
$ curl -i -X PUT \
> --header 'Content-Type: application/json; charset=UTF-8' \
> --data '{"id": 1, "title":"Updated title"}' \
> https://jsonplaceholder.typicode.com/albums/1
HTTP/2 200
date: Mon, 10 Feb 2020 22:55:38 GMT
content-type: application/json; charset=utf-8
content-length: 41
set-cookie: __cfduid=db769ca955f447cc90b485e86f39fc2981581375338; expires=Wed, 11-Mar-20 22:55:38 GMT; path=/; domain=.typicode.com; HttpOnly; SameSite=Lax
x-powered-by: Express
vary: Origin, Accept-Encoding
access-control-allow-credentials: true
cache-control: no-cache
pragma: no-cache
expires: -1
x-content-type-options: nosniff
etag: W/"29-ntvTOAUv21D/iEA6e5jBnEjOPXo"
via: 1.1 vegur
cf-cache-status: DYNAMIC
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 5631abf69ab13adc-YVR
{
"id": 1,
"title": "Updated title"
}
Delete/DELETE a resource.
$ curl -i -X DELETE https://jsonplaceholder.typicode.com/posts/1
HTTP/2 200
date: Mon, 10 Feb 2020 22:59:55 GMT
content-type: application/json; charset=utf-8
content-length: 2
set-cookie: __cfduid=dea190c88efc87b2c7c8d2947b52ed8621581375595; expires=Wed, 11-Mar-20 22:59:55 GMT; path=/; domain=.typicode.com; HttpOnly; SameSite=Lax
x-powered-by: Express
vary: Origin, Accept-Encoding
access-control-allow-credentials: true
cache-control: no-cache
pragma: no-cache
expires: -1
x-content-type-options: nosniff
etag: W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8"
via: 1.1 vegur
cf-cache-status: DYNAMIC
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 5631b23f6ffbf4a6-YVR
{}
Here is a top Google search result that shows the confusion.
https://stackoverflow.com/questions/50014848/network-request-after-button-click-with-flutter
The original question is trying to find a way to relate POST with the cookbook's GET example.
The person who answered gives a poor solution that might call setState after dispose. This could happen if the back button is pressed before the POST request completes.
The following POST example app attempts to remain in the spirit of the GET recipe that is already in the cookbook. By that, I mean it uses FutureBuilder
and avoids getting into more complex state management architecture like Provider, Bloc, etc. Another main goal was to keep the FutureBuilder
builder
's internal structure of if
, else if
, and default case as close as possible to the cookbook's existing "Fetch data from internet" recipe. Other goals were to avoid checking mounted
, avoid calling setState
after dispose
, avoid adding post frame callbacks, avoid await
in the UI code, avoid try
and catch
.
The use of _futureAlbum
being set to null
or not is the key feature in this example code that I wanted to propose for the cookbook article on POST.
UPDATE: I updated the code below to be simpler after some discussion of the pros and cons of several options.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> createAlbum(final String title) async {
final http.Response response = await http.post(
'https://jsonplaceholder.typicode.com/albums',
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'title': title,
}),
);
if (response.statusCode == 201) {
return Album.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to create album.');
}
}
class Album {
final int id;
final String title;
Album({this.id, this.title});
factory Album.fromJson(final Map<String, dynamic> json) {
return Album(
id: json['id'],
title: json['title'],
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
Future<Album> _futureAlbum;
@override
Widget build(final BuildContext context) {
return MaterialApp(
title: 'Create Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Create Data Example'),
),
body: Center(
child: (this._futureAlbum == null)
? RaisedButton(
child: Text('Create Album'),
onPressed: () {
this.setState(() {
// The album name should come from a text input.
this._futureAlbum = createAlbum(
'Rubber Soul ${DateTime.now().millisecondsSinceEpoch}',
);
});
},
)
: FutureBuilder<Album>(
future: this._futureAlbum,
builder: (final BuildContext context, final AsyncSnapshot<Album> snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.title);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
),
),
),
);
}
}
@sfshaza2 Can you review and merge @hemanthrajv recipe for POST
requests?
We expect that both PUT
and DELETE
recipes will be quite similar to the POST
recipe so having the POST
recipe merged is a good sign that continuing is worthwhile.
Thanks.
The cookbook has the recipe Fetch data from the internet. Recipes also showing how to POST/PUT/DELETE data to the internet would be good to make a complete set of recipes for all four of the fundamental CRUD operations using
FutureBuilder
.For example, the main point in the POST and PUT recipes would be to show that the future would be
null
to begin with and there would be noinitState
. When the future isnull
, the widget will show the save button. When a user presses the save button, thensetState
to change the future to non-null
. When the future is non-null
then the UI will show spinner, error message, or something indicating success. Anyway, it would be a very helpful companion to the "Fetch data from the internet" recipe.With DELETE, there is an issue with what the future will return. It can't return
null
for success as thensnapshot.hasData
becomes complicated. It could return boolean valuetrue
.Making this all easy for someone new would help get them up to speed with Flutter with less rethinking about things others have already had to think through.
Perhaps POST could be covered in one recipe called "Create data on the internet". PUT in a recipe called "Update data on the internet". DELETE in a recipe called "Delete data from the internet".