概要
Flutterでhttpパッケージを使ってファイルをアップロードする必要がある場合があります。この場合、httpパッケージのMultipartRequest
を使うとファイルをアップロードできます。このブログポストでは、MultipartRequest
を使ってファイルをアップロードする方法と、それをテストする方法について説明します。
MultipartRequestでファイルをアップロードする
次は実際にhttpパッケージのMultipartRequest
を使ってファイルをアップロードする例です。
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');
}
});
}
}
httpパッケージのMultipartRequest
を使ってファイルをアップロードする部分をもう少し詳しく見ていきます。
ExampleAPI({
required this.token,
@visibleForTesting http.MultipartRequest? mockClient,
}) : httpClient = mockClient ??
http.MultipartRequest(
"POST",
Uri.parse('${ENV.apiServer}/api/app'),
);
MultiPartRequest
でファイルをアップロードするには、まずhttp.MultipartRequest
のインスタンスを作成する必要があります。
Map<String, String> data = {};
data['date'] = dateFormatForSearch(date);
data['status'] = '$status';
data['doc_type'] = '$docType';
data['note'] = note;
MultipartRequest
はファイル以外にも他の情報を一緒に送信できます。そのため、一緒に送信する他の情報を準備しました。
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));
}
}
ファイルを設定する関数はアップロードしたいファイルのパスを受け取ります。受け取ったファイルのパスをhttp.MultipartFile.fromPath
を使ってMultipartFile
に変換します。
複数のファイルをアップロードする場合は、receipt_img[]
のように[]
を使って配列で渡すことができます。
httpClient.headers.addAll({'Authorization': 'Bearer $token'});
httpClient.fields.addAll(data);
httpClient.files.addAll(files);
次はファイルをアップロードするためにBearer
トークンをヘッダーに設定し、準備したデータをfields
に追加します。準備したファイルは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');
}
});
最後はデータを送信し、レスポンスを受け取ります。受け取ったレスポンスをアプリに合わせて処理すれば完了です。
テストコード
http
パッケージのMultipartRequest
を使ってファイルをアップロードする関数をテストする方法について説明します。
まず、すべてのテストコードは次のようになります。
...
@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 = [];
}
もっと詳しく見ていきましょう。
...
@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 = [];
}
DI(Dependency Injection)
を使っているため、テスト用のMock
オブジェクトを作成し、setUp
関数を使ってMock
オブジェクトを初期化しました。
...
@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,
});
});
...
}
...
setUp
関数で事前に設定したMock
オブジェクトを使って、リクエストが成功した場合をテストしました。
...
@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');
}
});
}
...
最後は、レスポンスが失敗した場合をテストしました。
完了
これで、FlutterでhttpパッケージのMultipartRequest
を使ってファイルをアップロードする方法と、それをテストする方法について説明しました。
Flutterでファイルをアップロードする機能を実装する場合は、このブログを参考にして実装し、テストコードを書いてみてください。
私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!
アプリ広報
Deku
が開発したアプリを使ってみてください。Deku
が開発したアプリはFlutterで開発されています。興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。