change in network manager and login function

This commit is contained in:
amirrezaghabeli
2025-08-17 08:04:59 +03:30
parent 81d01d53ae
commit 6197887b2a
2 changed files with 266 additions and 114 deletions
+222 -58
View File
@@ -1,131 +1,295 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../config/app_config.dart';
import '../utils/logger.dart';
import '../utils/result.dart';
import 'app_exceptions.dart';
import 'network_connectivity.dart';
class NetworkManager {
final Dio mainDio;
final NetworkConnectivity _connectivity = NetworkConnectivity();
NetworkManager()
: mainDio = Dio(
BaseOptions(
// baseUrl: AppConfig.apiBaseUrl,
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
contentType: 'application/json',
headers: {'Accept': 'application/json'},
baseUrl: 'http://10.0.2.2:8081',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
),
);
Future<void> init() async {
await addInterceptors();
}
Future<void> addInterceptors() async {
final requestInterceptor = InterceptorsWrapper(
onRequest: (options, handler) {
// TODO: add more
appLogger.info('🌐 Request: ${options.method} ${options.path}');
appLogger.info('🌐 Headers: ${options.headers}');
return handler.next(options);
},
onResponse: (response, handler) {
appLogger.info(
'✅ Response: ${response.statusCode} ${response.requestOptions.path}',
);
return handler.next(response);
},
onError: (error, handler) {
appLogger.error(
'❌ Error: ${error.message} - ${error.requestOptions.path}',
);
appLogger.error('❌ Error type: ${error.type}');
return handler.next(error);
},
);
mainDio.interceptors.add(requestInterceptor);
}
Future<T> makeRequest<T>(
/// Enhanced makeRequest method with comprehensive error handling
Future<Result<T>> makeRequest<T>(
String endpoint,
T Function(Map<String, dynamic>) fromJson, {
String method = 'GET',
FormData? data,
Object? data,
Map<String, dynamic>? queryParameters,
}) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
appLogger.info('🚀 network request: $method $endpoint');
try {
// Check network connectivity first
if (!await _connectivity.hasInternetConnection()) {
appLogger.error('❌ No internet connection available');
return Result.error(
NetworkConnectionException('No internet connection available'),
);
}
SharedPreferences prefs = await SharedPreferences.getInstance();
Dio dioInstance = mainDio;
await addInterceptors();
Future<Response> makeApiCall(String token, String url) async {
Future<Response> makeApiCall(String token) async {
final headers =
token.isEmpty
? <String, String>{}
: {'Authorization': 'Bearer $token'};
final options = Options(
headers: headers,
validateStatus: (status) => status != null && status < 500,
);
switch (method.toUpperCase()) {
case 'POST':
return await dioInstance.post(
url + endpoint,
endpoint,
data: data,
options: Options(headers: headers),
queryParameters: queryParameters,
options: options,
);
case 'PUT':
return await dioInstance.put(
url + endpoint,
endpoint,
data: data,
options: Options(headers: headers),
queryParameters: queryParameters,
options: options,
);
case 'PATCH':
return await dioInstance.patch(
url + endpoint,
endpoint,
data: data,
options: Options(headers: headers),
queryParameters: queryParameters,
options: options,
);
case 'DELETE':
return await dioInstance.delete(
url + endpoint,
endpoint,
data: data,
options: Options(headers: headers),
queryParameters: queryParameters,
options: options,
);
case 'GET':
return await dioInstance.get(
url + endpoint,
data: data,
options: Options(headers: headers),
endpoint,
queryParameters: queryParameters,
options: options,
);
default:
return await dioInstance.get(
url + endpoint,
options: Options(headers: headers),
endpoint,
queryParameters: queryParameters,
options: options,
);
}
}
try {
String? bearerToken = prefs.getString('bearer');
// String? url = prefs.getString('url');
Response res = await makeApiCall(bearerToken ?? '', AppConfig.apiBaseUrl);
switch (res.statusCode) {
case 200:
case 201:
return fromJson(res.data);
case 401:
break;
case 403:
throw Exception('Forbidden: ${res.data['message']}');
case 404:
throw Exception('Not found: ${res.data['message']}');
case 423:
throw Exception('Locked: ${res.data['message']}');
case 500:
throw Exception('Internal server error: ${res.data['message']}');
default:
throw Exception('Error: ${res.data['message']}');
}
} on DioException catch (e) {
if (e.response?.statusCode == 401) {
// await api.refreshToken();
String? bearerToken = prefs.getString('bearer');
String? url = prefs.getString('url');
Response res = await makeApiCall(bearerToken ?? '', url ?? '');
// appLogger.info('🔗 Base URL: ${url ?? 'NOT SET'}');
appLogger.info(
'🔑 Bearer token: ${bearerToken != null ? 'PRESENT' : 'NOT SET'}',
);
// if (url == null || url.isEmpty) {
// appLogger.error('❌ Base URL not configured');
// return Result.error(BadRequestException('Base URL not configured'));
// }
Response res = await makeApiCall(bearerToken ?? '');
// Handle successful responses
if (res.statusCode == 200 || res.statusCode == 201) {
return fromJson(res.data);
appLogger.info('✅ Request successful: ${res.statusCode}');
try {
final result = fromJson(res.data);
appLogger.info('✅ Response parsed successfully');
return Result.ok(result);
} on Exception catch (e) {
appLogger.error('❌ Failed to parse response: $e');
return Result.error(
FetchDataException(
'Failed to parse response data',
res.statusCode,
e,
),
);
}
}
throw Exception(e.message ?? 'Error occurred');
// Handle specific HTTP error codes
return _handleHttpError(res);
} on DioException catch (e) {
return _handleDioException(e);
} on Exception catch (e) {
appLogger.error('Unexpected error in makeRequest: $e');
return Result.error(
FetchDataException('Unexpected error occurred', null, e),
);
}
} catch (e) {
throw Exception(e.toString());
}
throw Exception('Unhandled error occurred');
/// Handle HTTP error responses
Result<T> _handleHttpError<T>(Response response) {
final statusCode = response.statusCode;
final message = _extractErrorMessage(response.data);
switch (statusCode) {
case 400:
return Result.error(
BadRequestException(message, statusCode, response.data),
);
case 401:
return Result.error(
AuthenticationException(message, statusCode, response.data),
);
case 403:
return Result.error(
ForbiddenException(message, statusCode, response.data),
);
case 404:
return Result.error(
NotFoundException(message, statusCode, response.data),
);
case 423:
return Result.error(
ForbiddenException(
'Resource is locked: $message',
statusCode,
response.data,
),
);
case 429:
return Result.error(
RateLimitException(message, statusCode, response.data),
);
case 500:
case 502:
case 503:
case 504:
return Result.error(
ServerException(message, statusCode, response.data),
);
default:
return Result.error(
FetchDataException(message, statusCode, response.data),
);
}
}
/// Handle Dio exceptions
Result<T> _handleDioException<T>(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return Result.error(TimeoutException('Request timed out', e));
case DioExceptionType.connectionError:
return Result.error(
NetworkConnectionException('Connection error occurred', e),
);
case DioExceptionType.badResponse:
if (e.response?.statusCode == 401) {
// Handle token refresh logic here if needed
// For now, return authentication error
return Result.error(
AuthenticationException(
'Authentication failed',
e.response?.statusCode,
e,
),
);
}
return Result.error(
FetchDataException(
'Bad response from server',
e.response?.statusCode,
e,
),
);
case DioExceptionType.cancel:
return Result.error(
FetchDataException('Request was cancelled', null, e),
);
case DioExceptionType.unknown:
default:
if (e.error is SocketException) {
return Result.error(
NetworkConnectionException('No internet connection', e),
);
}
return Result.error(
FetchDataException('Network error occurred', null, e),
);
}
}
/// Extract error message from response data
String _extractErrorMessage(data) {
if (data == null) {
return 'Unknown error';
}
if (data is Map<String, dynamic>) {
return data['message'] ??
data['error'] ??
data['detail'] ??
'Unknown error';
}
if (data is String) {
return data;
}
return 'Unknown error';
}
}
+8 -20
View File
@@ -2,7 +2,6 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tm_app/data/services/model/login_response/login_response_model.dart';
@@ -19,31 +18,20 @@ class AuthService {
required String username,
required String password,
}) async {
var headers = {
'accept': 'application/json',
'Content-Type': 'application/json',
};
var data = jsonEncode({"password": "Nima.1998", "username": "nakhostin"});
var dio = Dio(
BaseOptions(baseUrl: 'http://10.0.2.2:8081', headers: headers),
);
var response = await dio.request(
var data = jsonEncode({"password": password, "username": username});
final result = await _networkManager.makeRequest(
'/admin/v1/profile/login',
options: Options(method: 'POST'),
method: 'POST',
(json) => LoginResponseModel.fromJson(json),
data: data,
);
if (response.statusCode == 200) {
if (result is Ok<LoginResponseModel>) {
final prefs = await SharedPreferences.getInstance();
final token = response.data['data']?['access_token'];
prefs.setString('token', token);
if (token != null) {
final token = result.value.data.accessToken;
await prefs.setString('bearer', token);
}
print(json.encode(response.data));
} else {
print(response.statusMessage);
}
return Result.ok(LoginResponseModel.fromJson(response.data));
return result;
}
}