Search code examples
flutterdarthttp-postfastapiflutter-image

Flutter receives 422 response from Fastapi when posting a PNG file


I have created a working localhost API with FastAPI. The POST takes a PNG, does some image processing and returns a PNG as expected when I click the 'try it out' button in the FastAPI generated docs: successful localhost api call for png The curl post command shows as follows:

curl -X 'POST' \
  'http://localhost:8345/api/predict' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'file=@test_img.png;type=image/png'

The image File is successfully retrieved from the image picker library. (Where the image1 object has been initialized as File image1; in the app page's class.

Future getImage() async {
    var imageTmp = await ImagePicker.pickImage(source: ImageSource.gallery);
    setState(() {
      image1 = imageTmp;
      print('Image Path $image1');
    });
  }

I tried to emulate the API call with the below function in Flutter.

  doUpload() {
    /*
    curl -X 'POST' \
  'http://192.168.178.26:8345/api/predict' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'file=@test_img.png;type=image/png'

     */
    var request = http.MultipartRequest(
      'POST',
      Uri.parse("http://<my locally hosted ip>:8345/api/predict"),
    );
    Map<String, String> headers = {"Content-type": "multipart/form-data"};
    request.files.add(
      http.MultipartFile(
        'image',
        image1.readAsBytes().asStream(),
        image1.lengthSync(),
        filename: 'filename',
        contentType: MediaType('image', 'png'),
      ),
    );
    request.headers.addAll(headers);
    print("request: " + request.toString());
    request.send().then((value) => print(value.statusCode));
  }

When I run the doUpload() function, a POST is successfully sent to the localhost API, but it returns a 422 error 'unprocessable entity'. What I tried:

  • I tried to set the image type in doUpload to jpg, jpeg, but I keep getting a 422 error.
  • I tried looking up where the image_picker is supposed to store the temporary file to see if it's stored correctly, but when I look at the generated filepath, I don't see the actual file and tmp folder: filepath: File: '/data/user/0/<my package name>/cache/image_picker3300408791299772729jpg'

looking at my local UI filepath, I see: enter image description here

It shows no folder named cache, so I can't inspect it like this. However, the image picker saves it with a jpg at the end (not .jpg, is this normal?)


Solution

  • To mimic that curl command exactly, use this: (I've used the convenience constructor for simplicity)

      final request = http.MultipartRequest(
        'POST',
        Uri.parse('http://<my locally hosted ip>:8345/api/predict'),
      );
    
      request.files.add(
        await http.MultipartFile.fromPath(
          'file', // NOTE - this value must match the 'file=' at the start of -F
          image1.path,
          contentType: MediaType('image', 'png'),
        ),
      );
    
      final response = await http.Response.fromStream(await request.send());
    
      print(response.body);