英文:
Flutter Dio: How To Upload FormData/BulkImages Using A Structured Class/Modal Of REST APIs
问题
以下是您提供的代码的翻译部分:
api_service.dart
import 'dart:io';
import 'package:dio/dio.dart;
import 'package:retrofit/http.dart';
import '/network/interceptor/logging_interceptor.dart';
import '/network/response/general_response.dart';
import 'app_url.dart';
part 'api_service.g.dart';
@RestApi(baseUrl: AppUrl.apiUrl)
abstract class ApiService {
factory ApiService(Dio dio, baseUrl) {
dio.options = BaseOptions(
receiveTimeout: 50000,
connectTimeout: 50000,
followRedirects: false,
validateStatus: (status) { return status! < 500; },
headers: {
'Authorization': 'Basic XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
});
dio.interceptors.add(Logging(dio: dio));
return _ApiService(dio, baseUrl: AppUrl.apiUrl);
}
// APIs EndPoints Request Bodies without Token In Header As It Is Added In LoggingInterceptor
@POST('/login')
Future<GeneralResponse> login(@Body() Map<String, dynamic> body);
@POST('/signup')
Future<GeneralResponse> signup(@Body() Map<String, dynamic> body);
@POST('/json_receive')
Future<GeneralResponse> json_receive(@Body() Map<String, dynamic> body);
@POST('/simple_multipart_receiving')
@MultiPart()
Future<GeneralResponse> simple_multipart_receiving(
@Part(name: 'id') int id,
@Part(name: 'data') String data,
@Part(name: 'images') File image,
);
@POST('/formdata_receiving')
@MultiPart()
Future<GeneralResponse> formdata_receiving(formData);
}
api_url.dart
import '/network/api_service.dart';
import 'package:dio/dio.dart' as dio;
class AppUrl {
static const String apiUrl = 'http://192.168.1.1/demo/api/';
static ApiService apiService = ApiService(dio.Dio(), AppUrl.apiUrl);
}
logging_interceptor.dart
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart;
import 'package:flutter/material.dart;
import '/theme/color.dart;
import '/utility/shared_preference.dart;
import '/utility/top_level_variables.dart;
import '../../dialog/error_dialog.dart;
class Logging extends Interceptor {
String endpoint = "";
final Dio dio;
Logging({
required this.dio,
});
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
endpoint = options.path;
options.baseUrl = UserPreferences.baseUrl.isNotEmpty ? UserPreferences.baseUrl : "http://192.168.1.1/demo/api/" ;
if (options.path != '/login' &&
options.path != '/signup') {
options.headers.addEntries([MapEntry("token", UserPreferences.AuthToken)]);
}
if (options.path == '/formdata_receiving' || options.path == '/simple_multipart_receiving') {
options.contentType = 'multipart/form-data';
}
return super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
if (response.data['message'] == 'Invalid Token or Token Expired') {
_tokenExpiredDialog(TopVariables.appNavigationKey.currentContext!);
}
else if (response.data['message'] == 'Invalid API Authorization') {
showErrorDialog(response.data['message']);
}
else if (response.data['error'] == 1) {
showErrorDialog(response.data['message'].toString());
}
return super.onResponse(response, handler);
}
@override
Future<void> onError(DioError err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 200) {
// Everything is Alright
}
else if (err.response?.statusCode == 101) {
Navigator.pop(TopVariables.appNavigationKey.currentContext!);
} else if (err.response?.statusCode == 404) {
showErrorDialog(err.error);
}
else if (err.response?.statusCode == 500) {
showErrorDialog(err.error);
}
else {
showErrorDialog(err.toString());
}
//return super.onError(err, handler);
if (_shouldRetryOnHttpException(err)) {
try {
handler.resolve(await DioHttpRequestRetrier(dio: dio).requestRetry(err.requestOptions).catchError((e) {
handler.next(err);
}));
} catch (e) {
handler.next(err);
}
} else {
handler.next(err);
}
}
bool _shouldRetryOnHttpException(DioError err) {
return err.type == DioErrorType.other && ((err.error is HttpException && err.message.contains('Connection closed before full header was received')));
}
}
_tokenExpiredDialog(BuildContext context) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // User Must Tap Button
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: AlertDialog(
contentPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
content: SingleChildScrollView(
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0),
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(10.0),
topLeft: Radius.circular(10.0),
),
color: CustomColor.primary),
child: Text(
'Authentication Expired',
textAlign: TextAlign.center,
style: TextStyle(
color: CustomColor.white,
),
),
),
SizedBox(
height: 10,
),
Container(
padding: const EdgeInsets.fromLTRB(10.0, 0.0, 10.0, 0.0),
child: Text(
'Your Authentication Has Been Expired. Please Login Again.',
textAlign: TextAlign center,
),
),
],
),
),
actions: <Widget>[
Container(
alignment: Alignment.center,
child: TextButton(
child: const Text(
'Ok',
style: TextStyle(color: CustomColor.white),
),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(CustomColor.primary),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0))),
onPressed: () async {
prefs.clear();
Navigator.of(context).pushNamedAndRemoveUntil('/SignIn', (Route<dynamic> route) => false);
},
),
)
]));
},
);
}
/// Dio Retrier
class DioHttpRequestRetrier {
final Dio dio;
DioHttpRequestRetrier({
required this.dio,
});
Future<Response> requestRetry(RequestOptions requestOptions) async {
return dio.request(
requestOptions.path,
cancelToken: requestOptions.cancelToken,
data: requestOptions.data,
onReceiveProgress: requestOptions.onReceiveProgress,
onSendProgress: requestOptions.onSendProgress,
<details>
<summary>英文:</summary>
My problem is different from a general concept. First checkout my current structure of Flutter App where I have to work without changing the structure.
## api_service.dart ##
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:retrofit/http.dart';
import '/network/interceptor/logging_interceptor.dart';
import '/network/response/general_response.dart';
import 'app_url.dart';
part 'api_service.g.dart';
@RestApi(baseUrl: AppUrl.apiUrl)
abstract class ApiService {
factory ApiService(Dio dio, baseUrl) {
dio.options = BaseOptions(
receiveTimeout: 50000,
connectTimeout: 50000,
followRedirects: false,
validateStatus: (status) { return status! < 500; },
headers: {
'Authorization': 'Basic XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
});
dio.interceptors.add(Logging(dio: dio));
return _ApiService(dio, baseUrl: AppUrl.apiUrl);
}
// APIs EndPoints Request Bodies without Token In Header As It Is Added In LoggingInterceptor
@POST('/login')
Future<GeneralResponse> login(@Body() Map<String, dynamic> body);
@POST('/signup')
Future<GeneralResponse> signup(@Body() Map<String, dynamic> body);
@POST('/json_receive')
Future<GeneralResponse> json_receive(@Body() Map<String, dynamic> body);
@POST('/simple_multipart_receiving')
@MultiPart()
Future<GeneralResponse> simple_multipart_receiving(
@Part(name: 'id') int id,
@Part(name: 'data') String data,
@Part(name: 'images') File image,
);
@POST('/formdata_receiving')
@MultiPart()
Future<GeneralResponse> formdata_receiving(formData);
}
## api_url.dart ##
import '/network/api_service.dart';
import 'package:dio/dio.dart' as dio;
class AppUrl {
static const String apiUrl = 'http://192.168.1.1/demo/api/';
static ApiService apiService = ApiService(dio.Dio(),AppUrl.apiUrl);
}
## logging_interceptor.dart ##
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import '/theme/color.dart';
import '/utility/shared_preference.dart';
import '/utility/top_level_variables.dart';
import '../../dialog/error_dialog.dart';
class Logging extends Interceptor {
String endpoint = "";
final Dio dio;
Logging({
required this.dio,
});
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
endpoint = options.path;
options.baseUrl = UserPreferences.baseUrl.isNotEmpty ? UserPreferences.baseUrl : "http://192.168.1.1/demo/api/" ;
if (options.path != '/login' &&
options.path != '/signup') {
options.headers.addEntries([MapEntry("token", UserPreferences.AuthToken)]);
}
if (options.path == '/formdata_receiving' || options.path == '/simple_multipart_receiving') {
options.contentType = 'multipart/form-data';
}
return super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
if (response.data['message'] == 'Invalid Token or Token Expired') {
_tokenExpiredDialog(TopVariables.appNavigationKey.currentContext!);
}
else if (response.data['message'] == 'Invalid API Authorization') {
showErrorDialog(response.data['message']);
}
else if (response.data['error'] == 1) {
showErrorDialog(response.data['message'].toString());
}
return super.onResponse(response, handler);
}
@override
Future<void> onError(DioError err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 200) {
// Everything is Alright
}
else if (err.response?.statusCode == 101) {
Navigator.pop(TopVariables.appNavigationKey.currentContext!);
} else if (err.response?.statusCode == 404) {
showErrorDialog(err.error);
}
else if (err.response?.statusCode == 500) {
showErrorDialog(err.error);
}
else {
showErrorDialog(err.toString());
}
//return super.onError(err, handler);
if (_shouldRetryOnHttpException(err)) {
try {
handler.resolve(await DioHttpRequestRetrier(dio: dio).requestRetry(err.requestOptions).catchError((e) {
handler.next(err);
}));
} catch (e) {
handler.next(err);
}
} else {
handler.next(err);
}
}
bool _shouldRetryOnHttpException(DioError err) {
return err.type == DioErrorType.other && ((err.error is HttpException && err.message.contains('Connection closed before full header was received')));
}
}
_tokenExpiredDialog(BuildContext context) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // User Must Tap Button
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: AlertDialog(
contentPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
content: SingleChildScrollView(
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0),
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(10.0),
topLeft: Radius.circular(10.0),
),
color: CustomColor.primary),
child: Text(
'Authentication Expired',
textAlign: TextAlign.center,
style: TextStyle(
color: CustomColor.white,
),
),
),
SizedBox(
height: 10,
),
Container(
padding: const EdgeInsets.fromLTRB(10.0, 0.0, 10.0, 0.0),
child: Text(
'Your Authentication Has Been Expired. Please Login Again.',
textAlign: TextAlign.center,
),
),
],
),
),
actions: <Widget>[
Container(
alignment: Alignment.center,
child: TextButton(
child: const Text(
'Ok',
style: TextStyle(color: CustomColor.white),
),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(CustomColor.primary),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)))),
onPressed: () async {
prefs.clear();
Navigator.of(context).pushNamedAndRemoveUntil('/SignIn', (Route<dynamic> route) => false);
},
),
)
]));
},
);
}
/// Dio Retrier
class DioHttpRequestRetrier {
final Dio dio;
DioHttpRequestRetrier({
required this.dio,
});
Future<Response> requestRetry(RequestOptions requestOptions) async {
return dio.request(
requestOptions.path,
cancelToken: requestOptions.cancelToken,
data: requestOptions.data,
onReceiveProgress: requestOptions.onReceiveProgress,
onSendProgress: requestOptions.onSendProgress,
queryParameters: requestOptions.queryParameters,
options: Options(
contentType: requestOptions.contentType,
headers: requestOptions.headers,
sendTimeout: requestOptions.sendTimeout,
receiveTimeout: requestOptions.receiveTimeout,
extra: requestOptions.extra,
followRedirects: requestOptions.followRedirects,
listFormat: requestOptions.listFormat,
maxRedirects: requestOptions.maxRedirects,
method: requestOptions.method,
receiveDataWhenStatusError: requestOptions.receiveDataWhenStatusError,
requestEncoder: requestOptions.requestEncoder,
responseDecoder: requestOptions.responseDecoder,
responseType: requestOptions.responseType,
validateStatus: requestOptions.validateStatus,
),
);
}
}
Now I am using the below code to call it and submit to REST API.
## This Is How I Call It And Working ##
Map<String,dynamic> reqBody = {
'organisation_id': UserPreferences.OrganizationId,
'id': incomingMap["id"],
'data': incomingMap["data"],
'status': 00,
};
ApiService apiService = ApiService(dio.Dio(), AppUrl.apiUrl);
GeneralResponse response = await apiService.json_receive(reqBody);
## This Is How I Call FORMDATA But Not Working, Why ##
var formData = {
'organisation_id': UserPreferences.OrganizationId,
'id': incomingMap["id"],
'data': incomingMap["data"],
'status': 00,
'files': [
MultipartFile.fromFileSync('./example/upload.txt', filename: 'upload.txt'),
MultipartFile.fromFileSync('./example/upload.txt', filename: 'upload.txt'),
]
};
ApiService apiService = ApiService(dio.Dio(), AppUrl.apiUrl);
GeneralResponse response = await apiService.formdata_receiving(formData);
</details>
# 答案1
**得分**: 0
问题已通过添加几行代码来解决。以下是它们。请继续使用上面共享的其余代码,并用以下代码替换最后两行。
api_service.dart
--
```dart
@POST('/formdata_receiving')
@MultiPart()
Future<GeneralResponse> formdata_receiving(@Body() FormData formData);
这是如何调用FORMDATA
var formData = FormData.fromMap({
'organisation_id': UserPreferences.OrganizationId,
'id': incomingMap["id"],
'data': incomingMap["data"],
'status': 00,
'files': [
MultipartFile.fromFileSync('./example/upload.txt', filename: 'upload.txt'),
MultipartFile.fromFileSync('./example/upload.txt', filename: 'upload.txt'),
]
});
ApiService apiService = ApiService(dio.Dio(), AppUrl.apiUrl);
GeneralResponse response = await apiService.formdata_receiving(formData);
flutter
现在一切都像魅力一样运行。
英文:
Problem is solved with adding few lines of codes. Here are they. Use the rest of the code as shared above and replace the last 2 code set with the below one.
api_service.dart
@POST('/formdata_receiving')
@MultiPart()
Future<GeneralResponse> formdata_receiving(@Body() FormData formData);
This Is How I Call FORMDATA
var formData = FormData.fromMap({
'organisation_id': UserPreferences.OrganizationId,
'id': incomingMap["id"],
'data': incomingMap["data"],
'status': 00,
'files': [
MultipartFile.fromFileSync('./example/upload.txt', filename: 'upload.txt'),
MultipartFile.fromFileSync('./example/upload.txt', filename: 'upload.txt'),
]
});
ApiService apiService = ApiService(dio.Dio(), AppUrl.apiUrl);
GeneralResponse response = await apiService.formdata_receiving(formData);
flutter
All is working like a charm now.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论