Flutter Dio: 如何使用REST API的结构化类/模型上传FormData/BulkImages

huangapple go评论47阅读模式
英文:

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(&#39;/formdata_receiving&#39;)
@MultiPart()
Future&lt;GeneralResponse&gt; formdata_receiving(@Body() FormData formData);

This Is How I Call FORMDATA

var formData = FormData.fromMap({
&#39;organisation_id&#39;: UserPreferences.OrganizationId,
&#39;id&#39;: incomingMap[&quot;id&quot;],
&#39;data&#39;: incomingMap[&quot;data&quot;],
&#39;status&#39;: 00,
&#39;files&#39;: [
MultipartFile.fromFileSync(&#39;./example/upload.txt&#39;, filename: &#39;upload.txt&#39;),
MultipartFile.fromFileSync(&#39;./example/upload.txt&#39;, filename: &#39;upload.txt&#39;),
]
});
ApiService apiService = ApiService(dio.Dio(), AppUrl.apiUrl);
GeneralResponse response = await apiService.formdata_receiving(formData);
flutter

All is working like a charm now.

huangapple
  • 本文由 发表于 2023年2月10日 15:11:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/75407939.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定