import 'dart:async'; import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:ftc_mobile_app/utilities/enums/api_method.dart'; import 'package:http/http.dart' as http; import 'package:ftc_mobile_app/ftc_mobile_app.dart'; import 'package:http_parser/src/media_type.dart'; import '../dialogs/app_dialogs.dart'; import 'logging_interceptor.dart'; class HttpRequestClient { HttpRequestClient._(); static final HttpRequestClient _instance = HttpRequestClient._(); factory HttpRequestClient() { return _instance; } static const int _kSecondsTimeout = 60; static const String _kTimeOutMessage = "Unable to process request"; static const String _kInternetIssue = "Your internet connection is not stable"; static const String _kOtherException = "Unable to process request"; Map errorResponse(String message) { return Map.of({"success": false, "message": message}); } Map _dioErrorHandler(DioException e) { if (e.type == DioExceptionType.connectionError) { return errorResponse(_kInternetIssue); } // The request was made and the server responded with a status code // that falls out of the range of 2xx and is also not 304. if (e.response != null) { debugPrint('Dio error!'); debugPrint('STATUS: ${e.response?.statusCode}'); debugPrint('DATA: ${e.response?.data}'); debugPrint('HEADERS: ${e.response?.headers}'); if (e.response!.statusCode == HttpStatus.unauthorized || e.response!.statusCode == HttpStatus.forbidden) { AppDialog.showUnauthorizedAlert(); return errorResponse(''); } if (e.response!.data != null && e.response!.data is Map && (e.response!.data as Map).containsKey('message')) { return errorResponse(e.response!.data['message'].toString()); } // if (e.response!.statusCode == 401) { // return decoder(errorResponse(e.response!.data.toString())); // } } else { // Error due to setting up or sending the request debugPrint('Error sending request!'); debugPrint(e.message); } debugPrint('Dio ERROR ${e.error}\n'); return errorResponse(_kOtherException); } Map exceptionHandler(e) { debugPrint("Web Error: $e"); debugPrint("e Type: ${e.runtimeType.toString()}"); if (e is SocketException) { return errorResponse(_kInternetIssue); } else if (e is TimeoutException) { return errorResponse(_kInternetIssue); } else if (e is FormatException) { return errorResponse(_kOtherException); } else { return errorResponse(_kOtherException); } } //---------------------------------------------------------------- Future> getRequestHeader({ bool isBearer = true, bool isContentType = true, }) async { String token = LocalStorageManager.getLoginToken(); // //todo remove this in end // String token = // 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhIjp0cnVlLCJuIjoiSmFtc2hhaWQgU2FiaXIiLCJlIjoiamFtc2hhaWRzYWJpcjQxMTk4MEBnbWFpbC5jb20iLCJkIjoiNjQ2NTEyN2E3MmJjNzEzY2E4NDYwY2IzIiwicCI6Ii91cGxvYWRzL2RwL2RlZmF1bHQucG5nIiwiciI6Il9hIiwiaWF0IjoxNjkzMjIzNTIzfQ.EtpS_o0kEhdlNzCyFdFNNQzHd0IipTw8BEuUBVgfYok'; Map header = { 'Authorization': isBearer ? 'Bearer $token' : token, if (isContentType) 'Content-Type': 'application/json' }; return header; } Future getRequestWithOutHeader({required String url}) async { try { http.Response response = await http .get( Uri.parse(url), ) .timeout( const Duration( seconds: _kSecondsTimeout, ), ); ResponseModel responseModel = ResponseModel(); if (response.body.isNotEmpty && response.body.length > 4) { responseModel.statusCode = response.statusCode; responseModel.statusDescription = "Success"; responseModel.data = response.body; } // log('----------------response model is---------------${responseModel.toString()}'); return responseModel; } on TimeoutException catch (e) { return Future.value( ResponseModel.named( statusCode: 408, statusDescription: _kTimeOutMessage, data: e.toString(), ), ); } on SocketException catch (e) { return Future.value( ResponseModel.named( statusCode: 400, statusDescription: _kInternetIssue, data: e.toString(), ), ); } catch (e) { return Future.value( ResponseModel.named( statusCode: 500, statusDescription: _kOtherException, data: e.toString(), ), ); } } Future getRequestWithRequestBody({ required String url, dynamic requestBody, Map? requestHeader, }) async { try { Map header = {}; if (requestHeader == null) { header = await getRequestHeader(); } final uri = Uri.parse(url); final uriWithBody = uri.replace( queryParameters: requestBody, ); http.Response response = await http .get( uriWithBody, headers: requestHeader ?? header, ) .timeout( const Duration( seconds: _kSecondsTimeout, ), ); ResponseModel responseModel = ResponseModel(); if ((response.statusCode >= 200 && response.statusCode <= 230)) { responseModel = ResponseModel.fromJson( jsonDecode(response.body), statusCode: response.statusCode, ); } else { responseModel = ResponseModel.errorFromJson( jsonDecode(response.body), statusCode: response.statusCode, ); } // log('----------------response model is---------------${responseModel.toString()}'); return responseModel; } on TimeoutException catch (e) { return Future.value( ResponseModel.named( statusCode: 408, statusDescription: _kTimeOutMessage, data: e.toString(), ), ); } on SocketException catch (e) { return Future.value( ResponseModel.named( statusCode: 400, statusDescription: _kInternetIssue, data: e.toString(), ), ); } catch (e) { return Future.value( ResponseModel.named( statusCode: 500, statusDescription: _kOtherException, data: e.toString(), ), ); } } Future postRequest({ required String url, dynamic requestBody, bool doJsonEncodeRequestBody = false, bool isTokenRequired = false, dynamic requestHeader, }) async { try { ResponseModel responseModel = ResponseModel(); Map header = {}; if (isTokenRequired) { header = await getRequestHeader(isBearer: true); } http.Response response = await http .post( Uri.parse(url), body: doJsonEncodeRequestBody ? jsonEncode(requestBody) : requestBody, headers: isTokenRequired ? header : requestHeader, ) .timeout( const Duration( seconds: _kSecondsTimeout, ), ); if ((response.statusCode >= 200 && response.statusCode <= 230)) { responseModel = ResponseModel.fromJson( jsonDecode(response.body), statusCode: response.statusCode, ); } else { responseModel = ResponseModel.errorFromJson( jsonDecode(response.body), statusCode: response.statusCode, ); } // log('----------------response model is---------------${responseModel.toString()}'); return responseModel; } on TimeoutException catch (e) { return Future.value( ResponseModel.named( statusCode: 408, statusDescription: _kTimeOutMessage, data: e.toString(), ), ); } on SocketException catch (e) { return Future.value( ResponseModel.named( statusCode: 400, statusDescription: _kInternetIssue, data: e.toString(), ), ); } catch (e) { return Future.value( ResponseModel.named( statusCode: 500, statusDescription: _kOtherException, data: e.toString(), ), ); } } Future uploadImageRequest({ required String url, required String filePath, }) async { try { ResponseModel responseModel = ResponseModel(); Map header = {}; header = await getRequestHeader(isBearer: true); http.MultipartRequest request = http.MultipartRequest( "POST", Uri.parse(url), ); request.files.add( await http.MultipartFile.fromPath( "photo", filePath, ), ); request.headers.addAll(header); http.StreamedResponse response = await request.send().timeout( const Duration( seconds: _kSecondsTimeout, ), ); if ((response.statusCode >= 200 && response.statusCode <= 230)) { responseModel = ResponseModel.fromJson( jsonDecode(await response.stream.bytesToString()), statusCode: response.statusCode, ); } else { responseModel = ResponseModel.errorFromJson( jsonDecode(await response.stream.bytesToString()), statusCode: response.statusCode, ); } // log('----------------response model is---------------${responseModel.toString()}'); return responseModel; } on TimeoutException catch (e) { return Future.value( ResponseModel.named( statusCode: 408, statusDescription: _kTimeOutMessage, data: e.toString(), ), ); } on SocketException catch (e) { return Future.value( ResponseModel.named( statusCode: 400, statusDescription: _kInternetIssue, data: e.toString(), ), ); } catch (e) { return Future.value( ResponseModel.named( statusCode: 500, statusDescription: _kOtherException, data: e.toString(), ), ); } } Future patchRequest({ required String url, dynamic requestBody, bool isBearerHeaderRequired = false, bool isContentType = true, }) async { try { ResponseModel responseModel = ResponseModel(); Map header = {}; if (isBearerHeaderRequired) { header = await getRequestHeader( isBearer: true, isContentType: isContentType, ); } http.Request request = http.Request( 'PATCH', Uri.parse(url), ); request.body = jsonEncode(requestBody); request.headers.addAll(header); http.StreamedResponse response = await request.send().timeout( const Duration( seconds: _kSecondsTimeout, ), ); if ((response.statusCode >= 200 && response.statusCode <= 230)) { responseModel = ResponseModel.fromJson( jsonDecode(await response.stream.bytesToString()), statusCode: response.statusCode, ); } else { responseModel = ResponseModel.errorFromJson( jsonDecode(await response.stream.bytesToString()), statusCode: response.statusCode, ); } // log('----------------response model is---------------${responseModel.toString()}'); return responseModel; } on TimeoutException catch (e) { return Future.value( ResponseModel.named( statusCode: 408, statusDescription: _kTimeOutMessage, data: e.toString(), ), ); } on SocketException catch (e) { return Future.value( ResponseModel.named( statusCode: 400, statusDescription: _kInternetIssue, data: e.toString(), ), ); } catch (e) { return Future.value( ResponseModel.named( statusCode: 500, statusDescription: _kOtherException, data: e.toString(), ), ); } } Future deleteRequest({ required String url, bool isBearerHeaderRequired = false, dynamic requestBody, }) async { try { ResponseModel responseModel = ResponseModel(); Map header = {}; if (isBearerHeaderRequired) { header = await getRequestHeader(isBearer: true); } http.Request request = http.Request( 'DELETE', Uri.parse(url), ); request.headers.addAll(header); if (requestBody != null) { request.body = jsonEncode(requestBody); } http.StreamedResponse response = await request.send().timeout( const Duration( seconds: _kSecondsTimeout, ), ); if ((response.statusCode >= 200 && response.statusCode <= 230)) { responseModel = ResponseModel.fromJson( jsonDecode(await response.stream.bytesToString()), statusCode: response.statusCode, ); } else { responseModel = ResponseModel.errorFromJson( jsonDecode(await response.stream.bytesToString()), statusCode: response.statusCode, ); } // log('----------------response model is---------------${responseModel.toString()}'); return responseModel; } on TimeoutException catch (e) { return Future.value( ResponseModel.named( statusCode: 408, statusDescription: _kTimeOutMessage, data: e.toString(), ), ); } on SocketException catch (e) { return Future.value( ResponseModel.named( statusCode: 400, statusDescription: _kInternetIssue, data: e.toString(), ), ); } catch (e) { return Future.value( ResponseModel.named( statusCode: 500, statusDescription: _kOtherException, data: e.toString(), ), ); } } Future customRequest( String requestType, { required String url, bool isBearerHeaderRequired = false, bool isBearer = true, dynamic requestBody, dynamic requestHeader, }) async { try { ResponseModel responseModel = ResponseModel(); Map header = {}; if (isBearerHeaderRequired) { header = await getRequestHeader(isBearer: isBearer); } http.Request request = http.Request( requestType, Uri.parse(url), ); request.headers.addAll(isBearerHeaderRequired ? header : requestHeader); request.body = json.encode(requestBody); log('--------------request.url----------------------------($requestType) ${request.url.origin}${request.url.path}'); log('--------------request.headers----------------------------${request.headers}'); log('--------------request.body----------------------------${request.body}'); http.StreamedResponse response = await request.send().timeout( const Duration( seconds: _kSecondsTimeout, ), ); if ((response.statusCode >= 200 && response.statusCode <= 230)) { responseModel = ResponseModel.fromJson( jsonDecode(await response.stream.bytesToString()), statusCode: response.statusCode, ); log("Api Response($url):\n${jsonEncode(responseModel.toJson())}"); } else { responseModel = ResponseModel.errorFromJson( jsonDecode(await response.stream.bytesToString()), statusCode: response.statusCode, ); print("FUTURE Expection"); print(responseModel); } // log('----------------response model is---------------${responseModel.toString()}'); return responseModel; } on TimeoutException catch (e) { print("TimeOut Expection"); print(e); return Future.value( ResponseModel.named( statusCode: 408, statusDescription: _kTimeOutMessage, data: e.toString(), ), ); } on SocketException catch (e) { print("SOCKET Expection"); print(e); return Future.value( ResponseModel.named( statusCode: 400, statusDescription: _kInternetIssue, data: e.toString(), ), ); } catch (e) { print("FUTURE Expection"); print(e); return Future.value( ResponseModel.named( statusCode: 500, statusDescription: _kOtherException, data: e.toString(), ), ); } } Future uploadVideoRequest({ required String uploadUrl, required Uint8List chunkStream, }) async { try { ResponseModel responseModel = ResponseModel(); http.StreamedRequest request = http.StreamedRequest( "PUT", Uri.parse(uploadUrl), ); request.headers.addAll({ 'Content-Length': "${chunkStream.length}", 'Content-Type': 'video/mp4', 'content-type': 'application/octet-stream' }); request.sink.add(chunkStream); request.sink.close(); http.StreamedResponse response = await request.send(); responseModel.statusCode = response.statusCode; responseModel.statusDescription = response.reasonPhrase ?? ''; responseModel.header = response.headers; return responseModel; } on TimeoutException catch (e) { return Future.value( ResponseModel.named( statusCode: 408, statusDescription: _kTimeOutMessage, data: e.toString(), ), ); } on SocketException catch (e) { return Future.value( ResponseModel.named( statusCode: 400, statusDescription: _kInternetIssue, data: e.toString(), ), ); } catch (e) { return Future.value( ResponseModel.named( statusCode: 500, statusDescription: _kOtherException, data: e.toString(), ), ); } } Future safeApiCall({ required ApiMethod method, required String url, Map? headers, Map? body, Map? param, }) async { try { // final hasInternet = await FrequentFunctions.hasInternetConnection; // debugPrint("hasInternet: $hasInternet"); // // if (!hasInternet) { // return decoder(errorResponse(errorMsgNoInternet)); // } Map customHeader = await getRequestHeader(); if (headers != null) customHeader.addAll(headers); final dio = Dio() ..interceptors.add(Logging()) ..options.headers.addAll(customHeader); if (headers != null) { dio.options.headers.addAll(headers); } Response response; switch (method) { case ApiMethod.get: response = await dio.get(url, queryParameters: param); break; case ApiMethod.post: response = await dio.post( url, data: (body == null) ? null : json.encode(body), queryParameters: param, ); break; case ApiMethod.put: response = await dio.put( url, data: (body == null) ? null : json.encode(body), queryParameters: param, ); break; case ApiMethod.patch: response = await dio.patch( url, data: (body == null) ? null : json.encode(body), queryParameters: param, ); break; case ApiMethod.delete: response = await dio.delete( url, data: (body == null) ? null : json.encode(body), queryParameters: param, ); break; } final Map map = jsonDecode(response.toString()); final statusFail = (map['status'] is String && (map['status'] as String).toLowerCase() == "fail"); final status400 = (map['status'] is int && map['status'] == 400); if (map.containsKey('status') && (statusFail || status400)) { return ResponseModel.named( statusCode: 0, statusDescription: map['message'], ); } return ResponseModel.fromJson(map)..statusCode = response.statusCode ?? 0; } on DioException catch (e) { return ResponseModel.named( statusCode: e.response?.statusCode ?? 0, statusDescription: _dioErrorHandler(e)['message'], ); } catch (e) { debugPrint("Web Error: $e"); return ResponseModel.named( statusCode: 0, statusDescription: exceptionHandler(e)['message'], ); } } Future safeFormDataRequest({ required String url, required FormData body, }) async { try { Map customHeader = await getRequestHeader(); final dio = Dio() ..interceptors.add(Logging()) ..options.headers.addAll(customHeader); final response = await dio.post( url, data: body, onSendProgress: (int sent, int total) { debugPrint('$sent $total'); }, ); return ResponseModel.fromJson(jsonDecode(response.toString())) ..statusCode = response.statusCode ?? 0; } on DioException catch (e) { return ResponseModel.named( statusCode: e.response?.statusCode ?? 0, statusDescription: _dioErrorHandler(e)['message'], ); } catch (e) { debugPrint("Web Error: $e"); return ResponseModel.named( statusCode: 0, statusDescription: exceptionHandler(e)['message'], ); } } Future postMultipartRequest( {required String url, Map fields = const {}, Map files = const {}}) async { try { Map customHeader = await getRequestHeader(); // customHeader['Connection'] = 'keep-alive'; // customHeader['Accept'] = 'application/json'; // customHeader['Content-Type'] = 'multipart/form-data'; http.MultipartRequest request = http.MultipartRequest('POST', Uri.parse(url)); request.headers.addAll(customHeader); request.fields.addAll(fields); for (MapEntry file in files.entries) { String type = file.value.substring(file.value.lastIndexOf('.') + 1); String name = file.value.substring(file.value.lastIndexOf('/') + 1); request.files.add(await http.MultipartFile.fromPath( file.key, file.value, filename: name, contentType: MediaType('image', type), )); } http.StreamedResponse streamedResponse = await request.send(); http.Response httpResponse = await http.Response.fromStream(streamedResponse); log('────────────────────url> $url'); log('────────────────────files> $files'); log('────────────────────fields> $fields'); log('────────────────────Response.body> ${httpResponse.body}'); ResponseModel response = ResponseModel.fromJson(jsonDecode(httpResponse.body)); return Future.value(response); } on HttpException catch (e) { return Future.value(ResponseModel.named( statusCode: 405, statusDescription: e.message, data: e.toString())); } on TimeoutException { return Future.value(ResponseModel.named( statusCode: 408, statusDescription: "Request TimeOut", data: "Request TimeOut")); } on SocketException { return Future.value(ResponseModel.named( statusCode: 400, statusDescription: "Bad Request", data: "Bad Request")); } catch (e) { return Future.value(ResponseModel.named( statusCode: 500, statusDescription: "Service Error", data: "Service Error")); } } }