Skip to content

Commit 0ca5174

Browse files
committed
feat: file upload as request body
1 parent baea8ae commit 0ca5174

File tree

6 files changed

+86
-7
lines changed

6 files changed

+86
-7
lines changed

lib/codegen/dart/dio.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class DartDioCodeGen {
8383
case ContentType.text:
8484
dataExp = declareFinal('data').assign(strContent);
8585
// when add new type of [ContentType], need update [dataExp].
86-
case ContentType.formdata:
86+
default:
8787
dataExp = declareFinal('data').assign(refer('dio.FormData()'));
8888
}
8989
}

lib/consts.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,8 @@ const kSubTypeDefaultViewOptions = 'all';
348348
enum ContentType {
349349
json("$kTypeApplication/$kSubTypeJson"),
350350
text("$kTypeText/$kSubTypePlain"),
351-
formdata("$kTypeMultipart/$kSubTypeFormData");
351+
formdata("$kTypeMultipart/$kSubTypeFormData"),
352+
file("$kTypeApplication/$kSubTypeOctetStream");
352353

353354
const ContentType(this.header);
354355
final String header;

lib/models/request_model.dart

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class RequestModel {
2626
this.isParamEnabledList,
2727
this.requestBodyContentType = ContentType.json,
2828
this.requestBody,
29+
this.requestFile,
2930
this.requestFormDataList,
3031
this.responseStatus,
3132
this.message,
@@ -45,6 +46,7 @@ class RequestModel {
4546
final List<bool>? isParamEnabledList;
4647
final ContentType requestBodyContentType;
4748
final String? requestBody;
49+
final String? requestFile;
4850
final List<FormDataModel>? requestFormDataList;
4951
final int? responseStatus;
5052
final String? message;
@@ -67,8 +69,11 @@ class RequestModel {
6769
requestBodyContentType == ContentType.formdata;
6870
bool get hasJsonContentType => requestBodyContentType == ContentType.json;
6971
bool get hasTextContentType => requestBodyContentType == ContentType.text;
72+
bool get hasFileContentType => requestBodyContentType == ContentType.file;
7073
int get contentLength => utf8.encode(requestBody ?? "").length;
71-
bool get hasBody => hasJsonData || hasTextData || hasFormData;
74+
int get fileContentLength => utf8.encode(requestFile ?? "").length;
75+
bool get hasBody =>
76+
hasJsonData || hasTextData || hasFormData || hasFileContentType;
7277
bool get hasJsonData =>
7378
kMethodsWithBody.contains(method) &&
7479
hasJsonContentType &&
@@ -81,6 +86,10 @@ class RequestModel {
8186
kMethodsWithBody.contains(method) &&
8287
hasFormDataContentType &&
8388
formDataMapList.isNotEmpty;
89+
bool get hasFileData =>
90+
kMethodsWithBody.contains(method) &&
91+
hasFileContentType &&
92+
fileContentLength > 0;
8493
List<FormDataModel> get formDataList =>
8594
requestFormDataList ?? <FormDataModel>[];
8695
List<Map<String, String>> get formDataMapList =>
@@ -112,6 +121,7 @@ class RequestModel {
112121
isParamEnabledList != null ? [...isParamEnabledList!] : null,
113122
requestBodyContentType: requestBodyContentType,
114123
requestBody: requestBody,
124+
requestFile: requestFile,
115125
requestFormDataList:
116126
requestFormDataList != null ? [...requestFormDataList!] : null,
117127
);
@@ -130,6 +140,7 @@ class RequestModel {
130140
List<bool>? isParamEnabledList,
131141
ContentType? requestBodyContentType,
132142
String? requestBody,
143+
String? requestFile,
133144
List<FormDataModel>? requestFormDataList,
134145
int? responseStatus,
135146
String? message,
@@ -155,6 +166,7 @@ class RequestModel {
155166
requestBodyContentType:
156167
requestBodyContentType ?? this.requestBodyContentType,
157168
requestBody: requestBody ?? this.requestBody,
169+
requestFile: requestFile ?? this.requestFile,
158170
requestFormDataList: formDataList != null ? [...formDataList] : null,
159171
responseStatus: responseStatus ?? this.responseStatus,
160172
message: message ?? this.message,
@@ -188,6 +200,7 @@ class RequestModel {
188200
requestBodyContentType = kDefaultContentType;
189201
}
190202
final requestBody = data["requestBody"] as String?;
203+
final requestFile = data["requestFile"] as String?;
191204
final requestFormDataList = data["requestFormDataList"];
192205
final responseStatus = data["responseStatus"] as int?;
193206
final message = data["message"] as String?;
@@ -217,6 +230,7 @@ class RequestModel {
217230
isParamEnabledList: isParamEnabledList,
218231
requestBodyContentType: requestBodyContentType,
219232
requestBody: requestBody,
233+
requestFile: requestFile,
220234
requestFormDataList: requestFormDataList != null
221235
? mapListToFormDataModelRows(List<Map>.from(requestFormDataList))
222236
: null,
@@ -239,6 +253,7 @@ class RequestModel {
239253
"isParamEnabledList": isParamEnabledList,
240254
"requestBodyContentType": requestBodyContentType.name,
241255
"requestBody": requestBody,
256+
"requestFile": requestFile,
242257
"requestFormDataList": rowsToFormDataMapList(requestFormDataList),
243258
"responseStatus": includeResponse ? responseStatus : null,
244259
"message": includeResponse ? message : null,
@@ -261,6 +276,7 @@ class RequestModel {
261276
"Enabled Params: ${isParamEnabledList.toString()}",
262277
"Request Body Content Type: ${requestBodyContentType.toString()}",
263278
"Request Body: ${requestBody.toString()}",
279+
"Request File: ${requestFile.toString()}",
264280
"Request FormData: ${requestFormDataList.toString()}",
265281
"Response Status: $responseStatus",
266282
"Response Message: $message",
@@ -284,6 +300,7 @@ class RequestModel {
284300
listEquals(other.isParamEnabledList, isParamEnabledList) &&
285301
other.requestBodyContentType == requestBodyContentType &&
286302
other.requestBody == requestBody &&
303+
other.requestFile == requestFile &&
287304
other.requestFormDataList == requestFormDataList &&
288305
other.responseStatus == responseStatus &&
289306
other.message == message &&
@@ -306,6 +323,7 @@ class RequestModel {
306323
isParamEnabledList,
307324
requestBodyContentType,
308325
requestBody,
326+
requestFile,
309327
requestFormDataList,
310328
responseStatus,
311329
message,

lib/providers/collection_providers.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ class CollectionStateNotifier
141141
List<bool>? isParamEnabledList,
142142
ContentType? requestBodyContentType,
143143
String? requestBody,
144+
String? requestFile,
144145
List<FormDataModel>? requestFormDataList,
145146
int? responseStatus,
146147
String? message,
@@ -158,6 +159,7 @@ class CollectionStateNotifier
158159
isParamEnabledList: isParamEnabledList,
159160
requestBodyContentType: requestBodyContentType,
160161
requestBody: requestBody,
162+
requestFile: requestFile,
161163
requestFormDataList: requestFormDataList,
162164
responseStatus: responseStatus,
163165
message: message,

lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:apidash/utils/file_utils.dart';
12
import 'package:flutter/material.dart';
23
import 'package:flutter_riverpod/flutter_riverpod.dart';
34
import 'package:apidash/providers/providers.dart';
@@ -50,6 +51,52 @@ class EditRequestBody extends ConsumerWidget {
5051
.update(selectedId, requestBody: value);
5152
},
5253
),
54+
ContentType.file => Align(
55+
alignment: Alignment.centerLeft,
56+
child: Row(
57+
children: [
58+
Expanded(
59+
child: Theme(
60+
data: Theme.of(context),
61+
child: ElevatedButton.icon(
62+
icon: const Icon(
63+
Icons.snippet_folder_rounded,
64+
size: 20,
65+
),
66+
style: ButtonStyle(
67+
shape: MaterialStatePropertyAll(
68+
RoundedRectangleBorder(
69+
borderRadius: BorderRadius.circular(6),
70+
),
71+
),
72+
),
73+
onPressed: () async {
74+
var pickedResult = await pickFile();
75+
if (pickedResult != null &&
76+
pickedResult.files.isNotEmpty &&
77+
pickedResult.files.first.path != null) {
78+
ref
79+
.read(collectionStateNotifierProvider
80+
.notifier)
81+
.update(selectedId,
82+
requestFile:
83+
pickedResult.files.first.path!);
84+
}
85+
},
86+
label: Text(
87+
ref.watch(selectedRequestModelProvider
88+
.select((value) => value?.requestFile)) ??
89+
kLabelSelectFile,
90+
textAlign: TextAlign.center,
91+
overflow: TextOverflow.ellipsis,
92+
style: kFormDataButtonLabelTextStyle,
93+
),
94+
),
95+
),
96+
),
97+
],
98+
),
99+
),
53100
_ => TextFieldEditor(
54101
key: Key("$selectedId-body"),
55102
fieldKey: "$selectedId-body-editor",

lib/services/http_service.dart

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,28 @@ Future<(http.Response?, Duration?, String?)> request(
1919
Uri requestUrl = uriRec.$1!;
2020
Map<String, String> headers = requestModel.enabledHeadersMap;
2121
http.Response response;
22-
String? body;
22+
Object? body;
2323
try {
2424
Stopwatch stopwatch = Stopwatch()..start();
2525
var isMultiPartRequest =
2626
requestModel.requestBodyContentType == ContentType.formdata;
2727
if (kMethodsWithBody.contains(requestModel.method)) {
2828
var requestBody = requestModel.requestBody;
29-
if (requestBody != null && !isMultiPartRequest) {
30-
var contentLength = utf8.encode(requestBody).length;
29+
var requestFile = (requestModel.requestFile != null &&
30+
requestModel.requestFile!.isNotEmpty)
31+
? File(requestModel.requestFile!)
32+
: null;
33+
if ((requestBody != null || requestFile != null) &&
34+
!isMultiPartRequest) {
35+
var requestFileBytes =
36+
requestBody == null ? await requestFile!.readAsBytes() : null;
37+
38+
var contentLength = requestBody != null
39+
? utf8.encode(requestBody).length
40+
: requestFileBytes!.length;
41+
3142
if (contentLength > 0) {
32-
body = requestBody;
43+
body = requestBody ?? requestFileBytes;
3344
headers[HttpHeaders.contentLengthHeader] = contentLength.toString();
3445
if (!requestModel.hasContentTypeHeader) {
3546
headers[HttpHeaders.contentTypeHeader] =

0 commit comments

Comments
 (0)