[Flutter] Use MultipartRequest in http package to upload files

2024-10-06 hit count image

Let's see how to upload files using MultipartRequest in the http package in Flutter and how to test it.

Outline

Sometimes, you need to upload files using the http package in Flutter. In this case, you can upload files using MultipartRequest in the http package. In this blog post, I will introduce how to upload files using MultipartRequest and how to test it.

MultipartRequest to upload files

The following is an example of uploading files using the MultipartRequest in the http package.

class ExampleAPI {
  final String token;
  final http.MultipartRequest httpClient;

  ExampleAPI({
    required this.token,
    @visibleForTesting http.MultipartRequest? mockClient,
  }) : httpClient = mockClient ??
            http.MultipartRequest(
              "POST",
              Uri.parse('${ENV.apiServer}/api/app'),
            );

  Future<InfoData> sendData({
    required DateTime date,
    required int status,
    required int docType,
    required String note,
    required String passportImage,
    String? certificationImage,
    List<String>? receiptImages,
  }) async {
    Map<String, String> data = {};
    data['date'] = dateFormatForSearch(date);
    data['status'] = '$status';
    data['doc_type'] = '$docType';
    data['note'] = note;

    List<MultipartFile> files = [];
    files.add(await http.MultipartFile.fromPath('passport_image', passportImage));
    if (jpnCertImg != null && jpnCertImg != '') {
      files.add(await http.MultipartFile.fromPath('cert_img', certificationImage));
    }
    if (receiptImages?.isNotEmpty == true) {
      for (var image in receiptImages!) {
        if (image == '') continue;
        files.add(await http.MultipartFile.fromPath('receipt_img[]', image));
      }
    }

    httpClient.headers.addAll({'Authorization': 'Bearer $token'});
    httpClient.fields.addAll(data);
    httpClient.files.addAll(files);

    final stream = await httpClient.send();
    return http.Response.fromStream(stream).then((response) {
      final data = jsonDecode(utf8.decode(response.bodyBytes));

      if (data['success'] == true) {
        return InfoData.fromJson(data['data']);
      } else {
        throw Exception('Unknown response');
      }
    });
  }
}

Let’s take a closer look at the part of uploading files using the MultipartRequest in the http package.

ExampleAPI({
  required this.token,
  @visibleForTesting http.MultipartRequest? mockClient,
}) : httpClient = mockClient ??
          http.MultipartRequest(
            "POST",
            Uri.parse('${ENV.apiServer}/api/app'),
          );

To upload files using the MultipartRequest, you first need to create an instance of http.MultipartRequest.

Map<String, String> data = {};
data['date'] = dateFormatForSearch(date);
data['status'] = '$status';
data['doc_type'] = '$docType';
data['note'] = note;

The MultipartRequest can send not only files but also other information. So, I prepared other information to send together.

List<MultipartFile> files = [];
files.add(await http.MultipartFile.fromPath('passport_image', passportImage));
if (jpnCertImg != null && jpnCertImg != '') {
  files.add(await http.MultipartFile.fromPath('cert_img', certificationImage));
}
if (receiptImages?.isNotEmpty == true) {
  for (var image in receiptImages!) {
    if (image == '') continue;
    files.add(await http.MultipartFile.fromPath('receipt_img[]', image));
  }
}

The function, which sets the file to upload, receives the path of the file to upload. The path of the file to upload is converted to MultipartFile using http.MultipartFile.fromPath.

When uploading multiple files, you can send them as an array by using [] like receipt_img[].

httpClient.headers.addAll({'Authorization': 'Bearer $token'});
httpClient.fields.addAll(data);
httpClient.files.addAll(files);

Now, set the Bearer token in the header to upload the file and add the prepared data to fields. And add the prepared file to files.

final stream = await httpClient.send();
return http.Response.fromStream(stream).then((response) {
  final data = jsonDecode(utf8.decode(response.bodyBytes));

  if (data['success'] == true) {
    return InfoData.fromJson(data['data']);
  } else {
    throw Exception('Unknown response');
  }
});

Finally, send the data and receive the response. Process the received response according to your app.

Test code

Next, let’s see how to test the function that uploads files using the MultipartRequest in the http package.

First, the full code is as follows.

...
@GenerateMocks([http.MultipartRequest])
void main() {
  final mockHttpMultipartRequest = CustomMockMultipartRequest();
  setUp(() {
    mockHttpMultipartRequest.headers.clear();
    mockHttpMultipartRequest.fields.clear();
    mockHttpMultipartRequest.files.clear();
    when(mockHttpMultipartRequest.send()).thenAnswer(
      (_) async {
        final responseBody = jsonEncode({
          'success': true,
          'data': {
            'amount': 1000,
            'commission': 3000,
          }
        });
        final stream = Stream.value(utf8.encode(responseBody));
        return http.StreamedResponse(stream, 200);
      },
    );
  });

  test('Success', () async {
    final result = await TaxRefundAPI(
      token: 'test_token',
      client: mockHttpMultipartRequest,
    ).sendData(
      permitDate: DateTime.parse('2022-01-01 01:04'),
      status: 91,
      docType: 2,
      note: '',
      passportImg: 'assets/images/passport_sample.jpg',
      certificationImage: 'assets/images/placeholder.png',
      receiptImages: [
        'assets/images/sample_receipt.png',
        'assets/images/sample_receipt.png',
        'assets/images/sample_receipt.png',
      ],
    );

    // Request parameters
    expect(
      mockHttpMultipartRequest.headers,
      {'Authorization': 'Bearer test_token'},
    );
    expect(
      mockHttpMultipartRequest.fields,
      {
        'date': '2022-01-01',
        'status': '91',
        'doc_type': '2',
        'note': '',
      },
    );
    expect(mockHttpMultipartRequest.files.length, 5);
    expect(mockHttpMultipartRequest.files[0].field, 'passport_image');
    expect(mockHttpMultipartRequest.files[0].filename, 'passport_sample.jpg');
    expect(mockHttpMultipartRequest.files[1].field, 'cert_img');
    expect(mockHttpMultipartRequest.files[1].filename, 'placeholder.png');
    expect(mockHttpMultipartRequest.files[2].field, 'receipt_img[]');
    expect(
      mockHttpMultipartRequest.files[2].filename,
      'sample_receipt.png',
    );
    expect(mockHttpMultipartRequest.files[3].field, 'receipt_img[]');
    expect(
      mockHttpMultipartRequest.files[3].filename,
      'sample_receipt.png',
    );
    expect(mockHttpMultipartRequest.files[4].field, 'receipt_img[]');
    expect(
      mockHttpMultipartRequest.files[4].filename,
      'sample_receipt.png',
    );

    // Response
    expect(result, isA<InfoData>());
    expect(result.toMap(), {
      'amount': 1000,
      'commission': 3000,
    });
  });

  test('Throw error when response is failed', () async {
    when(mockHttpMultipartRequest.send()).thenAnswer(
      (_) async {
        final responseBody = jsonEncode({
          'success': false,
        });
        final stream = Stream.value(utf8.encode(responseBody));
        return http.StreamedResponse(stream, 200);
      },
    );

    try {
      await TaxRefundAPI(
        token: 'test_token',
        client: mockHttpMultipartRequest,
      ).sendData(
        permitDate: DateTime.parse('2022-01-01 01:04'),
        status: 91,
        docType: 2,
        note: '',
        passportImg: 'assets/images/passport_sample.jpg',
        certificationImage: 'assets/images/placeholder.png',
        receiptImages: [
          'assets/images/sample_receipt.png',
          'assets/images/sample_receipt.png',
          'assets/images/sample_receipt.png',
        ],
      );
    } catch (e) {
      expect(e.toString(), 'Exception: Unknown response');
    }
  });
}

class CustomMockMultipartRequest extends MockMultipartRequest {
  @override
  final Map<String, String> headers = {};

  @override
  final Map<String, String> fields = {};

  @override
  final List<MultipartFile> files = [];
}

Let’s take a closer look at the test code.

...
@GenerateMocks([http.MultipartRequest])
void main() {
  final mockHttpMultipartRequest = CustomMockMultipartRequest();
  setUp(() {
    mockHttpMultipartRequest.headers.clear();
    mockHttpMultipartRequest.fields.clear();
    mockHttpMultipartRequest.files.clear();
    when(mockHttpMultipartRequest.send()).thenAnswer(
      (_) async {
        final responseBody = jsonEncode({
          'success': true,
          'data': {
            'amount': 1000,
            'commission': 3000,
          }
        });
        final stream = Stream.value(utf8.encode(responseBody));
        return http.StreamedResponse(stream, 200);
      },
    );
  });
  ...
}

class CustomMockMultipartRequest extends MockMultipartRequest {
  @override
  final Map<String, String> headers = {};

  @override
  final Map<String, String> fields = {};

  @override
  final List<MultipartFile> files = [];
}

I used DI(Dependency Injec), so I created a Mock object for testing and initialized the Mock object using the setUp function.

...
@GenerateMocks([http.MultipartRequest])
void main() {
  final mockHttpMultipartRequest = CustomMockMultipartRequest();
  ...
  test('Success', () async {
    final result = await TaxRefundAPI(
      token: 'test_token',
      client: mockHttpMultipartRequest,
    ).sendData(
      permitDate: DateTime.parse('2022-01-01 01:04'),
      status: 91,
      docType: 2,
      note: '',
      passportImg: 'assets/images/passport_sample.jpg',
      certificationImage: 'assets/images/placeholder.png',
      receiptImages: [
        'assets/images/sample_receipt.png',
        'assets/images/sample_receipt.png',
        'assets/images/sample_receipt.png',
      ],
    );

    // Request parameters
    expect(
      mockHttpMultipartRequest.headers,
      {'Authorization': 'Bearer test_token'},
    );
    expect(
      mockHttpMultipartRequest.fields,
      {
        'date': '2022-01-01',
        'status': '91',
        'doc_type': '2',
        'note': '',
      },
    );
    expect(mockHttpMultipartRequest.files.length, 5);
    expect(mockHttpMultipartRequest.files[0].field, 'passport_image');
    expect(mockHttpMultipartRequest.files[0].filename, 'passport_sample.jpg');
    expect(mockHttpMultipartRequest.files[1].field, 'cert_img');
    expect(mockHttpMultipartRequest.files[1].filename, 'placeholder.png');
    expect(mockHttpMultipartRequest.files[2].field, 'receipt_img[]');
    expect(
      mockHttpMultipartRequest.files[2].filename,
      'sample_receipt.png',
    );
    expect(mockHttpMultipartRequest.files[3].field, 'receipt_img[]');
    expect(
      mockHttpMultipartRequest.files[3].filename,
      'sample_receipt.png',
    );
    expect(mockHttpMultipartRequest.files[4].field, 'receipt_img[]');
    expect(
      mockHttpMultipartRequest.files[4].filename,
      'sample_receipt.png',
    );

    // Response
    expect(result, isA<InfoData>());
    expect(result.toMap(), {
      'amount': 1000,
      'commission': 3000,
    });
  });
  ...
}
...

By using the Mock object created earlier, I tested the case where the request was successfully processed.

...
@GenerateMocks([http.MultipartRequest])
void main() {
  final mockHttpMultipartRequest = CustomMockMultipartRequest();
  ...
  test('Throw error when response is failed', () async {
    when(mockHttpMultipartRequest.send()).thenAnswer(
      (_) async {
        final responseBody = jsonEncode({
          'success': false,
        });
        final stream = Stream.value(utf8.encode(responseBody));
        return http.StreamedResponse(stream, 200);
      },
    );

    try {
      await TaxRefundAPI(
        token: 'test_token',
        client: mockHttpMultipartRequest,
      ).sendData(
        permitDate: DateTime.parse('2022-01-01 01:04'),
        status: 91,
        docType: 2,
        note: '',
        passportImg: 'assets/images/passport_sample.jpg',
        certificationImage: 'assets/images/placeholder.png',
        receiptImages: [
          'assets/images/sample_receipt.png',
          'assets/images/sample_receipt.png',
          'assets/images/sample_receipt.png',
        ],
      );
    } catch (e) {
      expect(e.toString(), 'Exception: Unknown response');
    }
  });
}
...

Lastly, I tested the case where the response failed.

Completed

Done! We’ve seen how to upload files using MultipartRequest in the http package in Flutter and how to test it.

If you are implementing a file upload feature in Flutter, refer to this blog post and try implementing it and writing test code.

Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!

App promotion

You can use the applications that are created by this blog writer Deku.
Deku created the applications with Flutter.

If you have interested, please try to download them for free.

Posts