34

I'm trying to do a http post request and I need to specify the body as form-data, because the server don't take the request as raw.

This is what I'm doing:

import 'dart:convert';

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

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  postTest() async {
    final uri = 'https://na57.salesforce.com/services/oauth2/token';
    var requestBody = {
      'grant_type':'password',
      'client_id':'3MVG9dZJodJWITSviqdj3EnW.LrZ81MbuGBqgIxxxdD6u7Mru2NOEs8bHFoFyNw_nVKPhlF2EzDbNYI0rphQL',
      'client_secret':'42E131F37E4E05313646E1ED1D3788D76192EBECA7486D15BDDB8408B9726B42',
      'username':'[email protected]',
      'password':'ABC1234563Af88jesKxPLVirJRW8wXvj3D'
    };

    http.Response response = await http.post(
        uri,
        body: json.encode(requestBody),
    );

    print(response.body);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Container(
        child: Center(
          child: RaisedButton(
            child: Text('Press Here'),
            onPressed: (){
              postTest();
            },
          ),
        ),
      ),
    );
  }
}

This is the actual response:

{
    "error": "unsupported_grant_type",
    "error_description": "grant type not supported"
}

Actual response

This is the expected response:

{
    "access_token": "00D0b000000Bb08!AR8AQO.s8mAGXCbwV77FXNLQqc2vtl8g6_16miVbgWlQMsuNHsaf2IGLUwnMVXBOfAj19iznhqhwlPOi4tagvf7FFgiJJgoi",
    "instance_url": "https://na57.salesforce.com",
    "id": "https://login.salesforce.com/id/00D0b000000Bb08EAC/0050b000005nstiAAA",
    "token_type": "Bearer",
    "issued_at": "1567993324968",
    "signature": "1+Zd/dSh9i7Moh2U0nFJLdXkVHqPlPVU6emwdYzXDPk="
}

Expected response

You can test this on postman switching the body between raw (you get the actual response) and form-data (you get the expected response)

PS: The headers are temporary headers created by the client tool.

2
  • check this stackoverflow.com/a/67559979/6314955 Commented May 16, 2021 at 18:24
  • @EightRice if you want to send data to the server which accepts content-type: x-www-form-urlencoded, the above URL that I gave works. And it is for text data. But if you want to send multipart/form-data. like file or images to the server (binary data), use this ( developerlibs.com/2020/07/… ). That blog shows how to send text data as well. use the relevant one based on the server accepted content-type Commented May 22, 2021 at 0:43

12 Answers 12

38

Use Map instead, because body in http package only has 3 types: String, List<int> or Map<String, String>.

Try this:

final Uri uri = Uri.parse('https://na57.salesforce.com/services/oauth2/token');
    final map = <String, dynamic>{};
    map['grant_type'] = 'password';
    map['client_id'] = '3MVG9dZJodJWITSviqdj3EnW.LrZ81MbuGBqgIxxxdD6u7Mru2NOEs8bHFoFyNw_nVKPhlF2EzDbNYI0rphQL';
    map['client_secret'] = '42E131F37E4E05313646E1ED1D3788D76192EBECA7486D15BDDB8408B9726B42';
    map['username'] = '[email protected]';
    map['password'] = 'ABC1234563Af88jesKxPLVirJRW8wXvj3D';

http.Response response = await http.post(
    uri,
    body: map,
);
Sign up to request clarification or add additional context in comments.

5 Comments

If body is a Map, it's encoded as form fields using encoding. The content-type of the request will be set to "application/x-www-form-urlencoded". pub.dev/documentation/http/latest/http/Client/post.html
Seems using Map behaves more naturally like a post from an HTML form than the jsonEncode method used on the official flutter docs. Thanks. Fixed my issue.
map doesn't worked for me. I have to use Dio FormData, but I am wonder why I will get 400 error by sending a map ?
@mohammad what did you sending?
Bad state: Cannot set the body fields of a Request with content-type "application/json"
14

Use MultipartRequest class

A multipart/form-data request automatically sets the Content-Type header to multipart/form-data.

This value will override any value set by the user.

refer pub.dev doc here

For example:

 Map<String, String> requestBody = <String,String>{
     'field1':value1
  };
 Map<String, String> headers= <String,String>{
     'Authorization':'Basic ${base64Encode(utf8.encode('user:password'))}'
  };

  var uri = Uri.parse('http://localhost.com');
  var request = http.MultipartRequest('POST', uri)
    ..headers.addAll(headers) //if u have headers, basic auth, token bearer... Else remove line
    ..fields.addAll(requestBody);
  var response = await request.send();
  final respStr = await response.stream.bytesToString();
  return jsonDecode(respStr);

Hope this helps

1 Comment

If someone's wondering like I was, you can use await http.Response.fromStream(streamedResponse) to get the usual response object from the streamedresponse
12

There is a dart package dio
it works like a charm, am using it as a standard to do http requests.
Please read the docs too on sending form data with dio package

import 'package:dio/dio.dart';    

postData(Map<String, dynamic> body)async{    
var dio = Dio();
try {
      FormData formData = new FormData.fromMap(body);
      var response = await dio.post(url, data: formData);
      return response.data;
    } catch (e) {
      print(e);
    }
}

2 Comments

From where you get the url when the is no parameter in Map
that's a snippet of code. Hopefully leading to the desired answer
4

So, you wanna send the body as form-data right? maybe you can try this? for me it's work

postTest() async {
    final uri = 'https://na57.salesforce.com/services/oauth2/token';
    var requestBody = {
      'grant_type':'password',
      'client_id':'3MVG9dZJodJWITSviqdj3EnW.LrZ81MbuGBqgIxxxdD6u7Mru2NOEs8bHFoFyNw_nVKPhlF2EzDbNYI0rphQL',
      'client_secret':'42E131F37E4E05313646E1ED1D3788D76192EBECA7486D15BDDB8408B9726B42',
      'username':'[email protected]',
      'password':'ABC1234563Af88jesKxPLVirJRW8wXvj3D'
    };

    http.Response response = await http.post(
        uri,
        body: requestBody,
    );

    print(response.body);
  }

Or

postTest() async {
    final uri = 'https://na57.salesforce.com/services/oauth2/token';

    http.Response response = await http.post(
        uri, body: {
      'grant_type':'password',
      'client_id':'3MVG9dZJodJWITSviqdj3EnW.LrZ81MbuGBqgIxxxdD6u7Mru2NOEs8bHFoFyNw_nVKPhlF2EzDbNYI0rphQL',
      'client_secret':'42E131F37E4E05313646E1ED1D3788D76192EBECA7486D15BDDB8408B9726B42',
      'username':'[email protected]',
      'password':'ABC1234563Af88jesKxPLVirJRW8wXvj3D'
    });

    print(response.body);
  }

Comments

3

Edit 1 (this worked for code login flow):

String url = "https://login.salesforce.com/services/oauth2/token";
http.post(url, body: {
  "grant_type": "authorization_code",
  "client_id": "some_client_id",
  "redirect_uri": "some_redirect_uri",
  "code": "some_code_generated_by_salesforce_login",
  "client_secret": "some_client_secret",
}).then((response) {
  //--handle response
});

give 'FormData' a try from:

import 'package:dio/dio.dart';

FormData formData = new FormData.fromMap(dataMap);

retrofitClient.getToken(formData).then((response){//--handle respnse--});

'retrofitClient' is from package retrofit: ^1.0.1+1

1 Comment

Invalid request body \"Instance of 'FormData'\".
2

Can you try this;

    String url = 'https://myendpoint.com';
      Map<String, String> headers = {
"Content-Type": "application/x-www-form-urlencoded"    
"Content-type": "application/json"};
      String json = '{"grant_type":"password",
        "username":"[email protected]",
        "password":"123456"}';
      // make POST request
      Response response = await post(url, headers: headers, body: json);
      // check the status code for the result
      int statusCode = response.statusCode;
      // this API passes back the id of the new item added to the body
      String body = response.body;

4 Comments

Use or not the header don't make difference, my point is how do I send the body as form-data
I've updated my question too, now you are able to test something closer to my real situation.
@M.Massula i think in flutter, there is no any option to request using form data. it's possible only, you can pass data using json.
this is missing a comma between the header strings.
2

This is my example with form data function

 Future<ResponseModel> postWithFormData(String url, List<File> files,
      {Map<String, String> body = const {}, bool throwAlert = false}) async {
    var request = http.MultipartRequest("POST", Uri.parse(localApiHost + url));

    request.headers
        .addAll({"Authorization": "Bearer ${Storage.getString(token)}"});
    request.fields.addAll(body);
    for (var file in files) {
      request.files.add(await http.MultipartFile.fromPath("files", file.path));
    }
    var sendRequest = await request.send();
    var response = await http.Response.fromStream(sendRequest);
    final responseData = json.decode(response.body);
    if (response.statusCode >= 400 && throwAlert) {
      showErrorDialog(responseData["message"]);
    }
    return ResponseModel(body: responseData, statusCode: response.statusCode);
  }

Comments

1

This code snippets successfully executes a POST api call which expect an authorization token and form-data.

final headers = {'Authorization': 'Bearer $authToken'};
  var requestBody = {
    'shopId': '5',
    'fromDate': '01/01/2021',
    'toDate': '01/10/2022',
  };

  final response = await http.post(
    Uri.parse(
        'https://api.sample.com/mobile/dashboard/getdetails'),
    headers: headers,
    body: requestBody,
  );

  print("RESPONSE ${response.body}");

Comments

0

Using POSTMAN to test the query and get the format is quite useful. This is allow you to see if you really need to set Headers. See my example below. I hope it helps and it is not too much

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

class RegisterUser{    
  String fullname;
  String phonenumber;
  String emailaddress;
  String password;
  Map data;    
  RegisterUser({this.fullname, this.phonenumber, this.emailaddress, this.password});    
  Future<void> registeruseraction() async {    
    String url = 'https://api.url.com/';
    Response response = await post(url, body: {
      'fullname' : fullname,
      'phonenumber' : phonenumber,
      'emailaddress' : emailaddress,
      'password' : password
    });    
    print(response.body);
    data = jsonDecode(response.body);    
  }    
}

Comments

0

You can also use MultiPartRequest, it will work for sure

  var request = new 
  http.MultipartRequest("POST",Uri.parse("$baseUrl/example/"));

  request.headers.addAll(baseHeader);
  request.fields['id'] = params.id.toString();
  request.fields['regionId'] = params.regionId.toString();
  request.fields['districtId'] = params.districtId.toString(); 
  http.Response response = await http.Response.fromStream(await 
  request.send());

  print('Uploaded! ${response.body} ++ ${response.statusCode}');
 

Comments

0

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

// Function to make the POST request
Future<http.Response> post(String url, Map<String, String> body) async {
  // Encode the body of the request as JSON
  var encodedBody = json.encode(body);

  // Make the POST request
  var response = await http.post(url,
      headers: {"Content-Type": "application/json"}, body: encodedBody);

  // Return the response
  return response;
}

Comments

0

The same error message shown to me. I just add Authorization into the header. Example:


Before

Options header = Options(
        headers: {
          'Accept': '*/*',
         'Access-Control-Allow-Origin': '*',
          'Content-Type': 'multipart/form-data'
        },
        sendTimeout: const Duration(seconds: 120),
        receiveTimeout: const Duration(seconds: 30),
      );

After

Options header = Options(
        headers: {
          'Accept': '*/*',
         'Access-Control-Allow-Origin': '*',
    enter code here
          'Content-Type': 'multipart/form-data',
          ***'Authorization' : 'Bearer ${GetIt.I.get<LocalSharedPreferences>().getCurrentUserToken()}'***
        },
        sendTimeout: const Duration(seconds: 120),
        receiveTimeout: const Duration(seconds: 30),
      );

Then only the error 403 is gone. Then, I research a bit on it. The real problem is some API's not working without Authorization. In my case, I used laravel as my backend, for that I have used Laravel api. So, for laravel the authorization needed.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.