Refactor API error handling and add try-catch blocks

Added try-catch blocks for error handling in API's files to rethrow the errors to the cubit so cubits can update the UI based on them.

Refactored error handling in HTTPInterceptor and HTTPService classes.
This commit is contained in:
Mohammad Salameh
2024-04-15 15:47:13 +03:00
parent dd90a2133f
commit df13c66b1a
9 changed files with 247 additions and 191 deletions

View File

@ -125,14 +125,17 @@ class HomeCubit extends Cubit<HomeState> {
emitSafe(GetSpacesLoading());
try {
spaces = await SpacesAPI.getSpaces();
selectedSpace = spaces!.isNotEmpty
?
// selectSpace(spaces!.first)
selectedSpace = spaces!.first
: null;
emitSafe(GetSpacesLoaded(spaces!));
} on ServerFailure catch (failure) {
emitSafe(GetSpacesError(failure.errMessage));
} catch (failure) {
emitSafe(GetSpacesError(failure.toString()));
return;
}
if (spaces != null && spaces!.isNotEmpty) {
selectedSpace = spaces!.first;
emitSafe(GetSpacesSuccess(spaces!));
fetchRooms(selectedSpace!);
} else {
emitSafe(GetSpacesError("No spaces found"));
}
}
@ -140,13 +143,14 @@ class HomeCubit extends Cubit<HomeState> {
emitSafe(GetSpaceRoomsLoading());
try {
space.rooms = await SpacesAPI.getRoomsBySpaceId(space.id!);
if (space.rooms != null) {
emitSafe(GetSpaceRoomsLoaded(space.rooms!));
} else {
emitSafe(GetSpaceRoomsError("No rooms found"));
}
} on ServerFailure catch (failure) {
emitSafe(GetSpacesError(failure.errMessage));
} catch (failure) {
emitSafe(GetSpaceRoomsError(failure.toString()));
return;
}
if (space.rooms != null) {
emitSafe(GetSpaceRoomsSuccess(space.rooms!));
} else {
emitSafe(GetSpaceRoomsError("No rooms found"));
}
}
@ -169,14 +173,6 @@ class HomeCubit extends Cubit<HomeState> {
),
],
'Devices': [
// IconButton(
// icon: Image.asset(
// Assets.iconsFilter,
// height: 20,
// width: 20,
// ),
// onPressed: () {},
// ),
IconButton(
icon: const Icon(
Icons.add,

View File

@ -4,33 +4,45 @@ abstract class HomeState {}
class HomeInitial extends HomeState {}
class GetSpacesLoading extends HomeState {}
//base states
class HomeLoading extends HomeState {}
class GetSpacesLoaded extends HomeState {
class HomeError extends HomeState {
final String errMessage;
HomeError(this.errMessage);
}
class HomeSuccess extends HomeState {}
///specific states
//get spaces
class GetSpacesLoading extends HomeLoading {}
class GetSpacesSuccess extends HomeSuccess {
final List<SpaceModel> spaces;
GetSpacesLoaded(this.spaces);
GetSpacesSuccess(this.spaces);
}
class GetSpacesError extends HomeState {
final String errMessage;
GetSpacesError(this.errMessage);
class GetSpacesError extends HomeError {
GetSpacesError(super.errMessage);
}
class GetSpaceRoomsLoading extends HomeState {}
//get rooms
class GetSpaceRoomsLoading extends HomeLoading {}
class GetSpaceRoomsLoaded extends HomeState {
class GetSpaceRoomsSuccess extends HomeSuccess {
final List<RoomModel> rooms;
GetSpaceRoomsLoaded(this.rooms);
GetSpaceRoomsSuccess(this.rooms);
}
class GetSpaceRoomsError extends HomeState {
final String errMessage;
GetSpaceRoomsError(this.errMessage);
class GetSpaceRoomsError extends HomeError {
GetSpaceRoomsError(super.errMessage);
}
//UI states
class SpaceSelected extends HomeState {
final SpaceModel space;

View File

@ -41,7 +41,7 @@ class AuthCubit extends Cubit<AuthState> {
static UserModel? user;
static Token token = Token.emptyConstructor();
/////////////////////////////////////API CALLS/////////////////////////////////////
login() async {
emit(AuthLoginLoading());
try {
@ -51,23 +51,23 @@ class AuthCubit extends Cubit<AuthState> {
password: passwordController.text,
),
);
if (token.accessTokenIsNotEmpty) {
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write(
key: Token.loginAccessTokenKey, value: token.accessToken);
const FlutterSecureStorage().write(
key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString());
user = UserModel.fromToken(token);
emailController.clear();
passwordController.clear();
emit(AuthLoginSuccess());
} else {
emit(AuthLoginError(message: 'Something went wrong'));
}
} on ServerFailure catch (failure) {
emit(AuthError(message: failure.errMessage));
} catch (failure) {
emit(AuthLoginError(message: failure.toString()));
return;
}
if (token.accessTokenIsNotEmpty) {
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write(
key: Token.loginAccessTokenKey, value: token.accessToken);
const FlutterSecureStorage().write(
key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString());
user = UserModel.fromToken(token);
emailController.clear();
passwordController.clear();
emit(AuthLoginSuccess());
} else {
emit(AuthLoginError(message: 'Something went wrong'));
}
}
@ -79,8 +79,9 @@ class AuthCubit extends Cubit<AuthState> {
NavigationService.navigatorKey.currentState!
.popAndPushNamed(Routes.authLogin);
emit(AuthLogoutSuccess());
} on ServerFailure catch (failure) {
emit(AuthError(message: failure.errMessage));
} catch (failure) {
emit(AuthLogoutError(message: failure.toString()));
return;
}
}
@ -109,11 +110,4 @@ class AuthCubit extends Cubit<AuthState> {
emit(AuthTokenError(message: "Something went wrong"));
}
}
static void logUserOut() async {
user = null;
token = Token.emptyConstructor();
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.delete(key: Token.loginAccessTokenKey);
}
}

View File

@ -251,32 +251,42 @@ class DevicesCubit extends Cubit<DevicesState> {
code: control.code,
));
try {
await DevicesAPI.controlDevice(control).then((response) {
emitSafe(DeviceControlSuccess(
code: control.code,
));
if (response['success'] ?? false) {
Future.delayed(const Duration(milliseconds: 400), () {
getDevicesStatues(
deviceId,
HomeCubit.getInstance().selectedSpace!.rooms!.indexOf(
HomeCubit.getInstance().selectedRoom!,
),
code: control.code);
});
} else {
emitSafe(DeviceControlError('Failed to control the device'));
}
});
} on ServerFailure catch (failure) {
emitSafe(DeviceControlError(failure.errMessage));
var response = await DevicesAPI.controlDevice(control);
if (response['success'] ?? false) {
emitSafe(DeviceControlSuccess(code: control.code));
//this delay is to give tuya server time to update the status
Future.delayed(const Duration(milliseconds: 400), () {
getDevicesStatues(
deviceId,
HomeCubit.getInstance()
.selectedSpace!
.rooms!
.indexOf(HomeCubit.getInstance().selectedRoom!),
code: control.code);
});
} else {
emitSafe(DeviceControlError('Failed to control the device'));
}
} catch (failure) {
emitSafe(DeviceControlError(failure.toString()));
return;
}
}
fetchGroups(int spaceId) async {
emitSafe(DevicesCategoriesLoading());
allCategories = await DevicesAPI.fetchGroups(spaceId);
emitSafe(DevicesCategoriesSuccess());
try {
allCategories = await DevicesAPI.fetchGroups(spaceId);
} catch (e) {
emitSafe(DevicesCategoriesError(e.toString()));
return;
}
if (allCategories!.isNotEmpty && allCategories != null) {
emitSafe(DevicesCategoriesSuccess());
} else {
emitSafe(DevicesCategoriesError('No Groups found'));
}
}
fetchDevicesByRoomId(int? roomId) async {
@ -287,46 +297,46 @@ class DevicesCubit extends Cubit<DevicesState> {
.selectedSpace!
.rooms!
.indexWhere((element) => element.id == roomId);
HomeCubit.getInstance().selectedSpace!.rooms![roomIndex].devices =
await SpacesAPI.getDevicesByRoomId(roomId);
try {
HomeCubit.getInstance().selectedSpace!.rooms![roomIndex].devices =
await DevicesAPI.getDevicesByRoomId(roomId);
} catch (e) {
emitSafe(GetDevicesError(e.toString()));
return;
}
emitSafe(GetDevicesSuccess());
//get status for each device
//TODO get devices status per page via page controller instead of getting all devices status at once
for (var device
in HomeCubit.getInstance().selectedSpace!.rooms![roomIndex].devices!) {
getDevicesStatues(device.id!, roomIndex);
}
emitSafe(GetDevicesSuccess());
}
getDevicesStatues(String deviceId, int roomIndex, {String? code}) async {
emitSafe(GetDeviceStatusLoading(code: code));
int deviceIndex = HomeCubit.getInstance()
.selectedSpace!
.rooms![roomIndex]
.devices!
.indexWhere((element) => element.id == deviceId);
List<StatusModel> statuses = [];
try {
emitSafe(GetDeviceStatusLoading(code: code));
int deviceIndex = HomeCubit.getInstance()
.selectedSpace!
.rooms![roomIndex]
.devices!
.indexWhere((element) => element.id == deviceId);
List<StatusModel> statuses = [];
var response = await DevicesAPI.getDeviceStatus(deviceId);
// if (response['result']['status'].length > 4)
// print('response : ${response['result']['status'][4]}');
for (var status in response['result']['status']) {
statuses.add(StatusModel.fromJson(status));
}
HomeCubit.getInstance()
.selectedSpace!
.rooms![roomIndex]
.devices![deviceIndex]
.status = statuses;
emitSafe(GetDeviceStatusSuccess(code: code));
} on ServerFailure catch (failure) {
emitSafe(
GetDeviceStatusError(failure.errMessage),
);
} catch (e) {
emitSafe(GetDeviceStatusError(e.toString()));
return;
}
HomeCubit.getInstance()
.selectedSpace!
.rooms![roomIndex]
.devices![deviceIndex]
.status = statuses;
emitSafe(GetDeviceStatusSuccess(code: code));
}
///Lights

View File

@ -12,10 +12,8 @@ class SceneCubit extends Cubit<SceneState> {
void getScenes() {
emit(SceneLoading());
//TODO: remove this it's causing the Bad State because its being after the cubit is closed
Future.delayed(const Duration(milliseconds: 50), () {
emit(SceneSuccess());
});
emit(SceneSuccess());
}
List<SceneModel> scenes = [];

View File

@ -1,5 +1,6 @@
import 'package:syncrow_app/features/devices/model/device_category_model.dart';
import 'package:syncrow_app/features/devices/model/device_control_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/services/api/api_links_endpoints.dart';
import 'package:syncrow_app/services/api/http_service.dart';
@ -8,18 +9,19 @@ class DevicesAPI {
static Future<Map<String, dynamic>> controlDevice(
DeviceControlModel controlModel) async {
// print(
// 'contoling [${controlModel.deviceId}] with code [${controlModel.code}] and value [${controlModel.value}');
final response = await _httpService.post(
path: ApiEndpoints.control,
body: controlModel.toJson(),
showServerMessage: false,
expectedResponseModel: (json) {
return json;
},
);
return response;
try {
final response = await _httpService.post(
path: ApiEndpoints.control,
body: controlModel.toJson(),
showServerMessage: false,
expectedResponseModel: (json) {
return json;
},
);
return response;
} catch (e) {
rethrow;
}
}
static Future<List<DevicesCategoryModel>> fetchGroups(int spaceId) async {
@ -28,24 +30,52 @@ class DevicesAPI {
"pageSize": 100,
"pageNo": 1
};
final response = await _httpService.get(
path: ApiEndpoints.groups,
queryParameters: params,
showServerMessage: false,
expectedResponseModel: (json) =>
DevicesCategoryModel.fromJsonList(json['groups']),
);
return response;
try {
final response = await _httpService.get(
path: ApiEndpoints.groups,
queryParameters: params,
showServerMessage: false,
expectedResponseModel: (json) =>
DevicesCategoryModel.fromJsonList(json['groups']),
);
return response;
} catch (e) {
rethrow;
}
}
static Future<Map<String, dynamic>> getDeviceStatus(String deviceId) async {
final response = await _httpService.get(
path: '${ApiEndpoints.deviceStatus}/$deviceId/functions/status',
showServerMessage: false,
expectedResponseModel: (json) {
return json;
},
);
return response;
try {
final response = await _httpService.get(
path: '${ApiEndpoints.deviceStatus}/$deviceId/functions/status',
showServerMessage: false,
expectedResponseModel: (json) {
return json;
},
);
return response;
} catch (e) {
rethrow;
}
}
static Future<List<DeviceModel>> getDevicesByRoomId(int roomId) async {
try {
final response = await _httpService.get(
path: ApiEndpoints.devicesByRoom,
queryParameters: {"roomId": roomId, "pageSize": 10},
showServerMessage: false,
expectedResponseModel: (json) {
List<DeviceModel> devices = [];
for (var device in json['devices']) {
devices.add(DeviceModel.fromJson(device));
}
return devices;
},
);
return response;
} catch (e) {
rethrow;
}
}
}

View File

@ -1,18 +1,30 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/auth/model/token.dart';
import 'package:syncrow_app/navigation/navigation_service.dart';
import 'package:syncrow_app/services/api/api_links_endpoints.dart';
import 'dart:async';
import 'dart:developer' as developer;
import 'package:syncrow_app/services/api/network_exception.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
class HTTPInterceptor extends InterceptorsWrapper {
@override
List<String> headerExclusionList = [];
List<String> headerExclusionListOfAddedParameters = [
ApiEndpoints.login,
];
@override
void onResponse(Response response, ResponseInterceptorHandler handler) async {
return handler.next(response);
if (await validateResponse(response)) {
super.onResponse(response, handler);
} else {
handler.reject(DioException(
requestOptions: response.requestOptions, response: response));
}
}
@override
@ -20,17 +32,20 @@ class HTTPInterceptor extends InterceptorsWrapper {
RequestOptions options, RequestInterceptorHandler handler) async {
var storage = const FlutterSecureStorage();
var token = await storage.read(key: Token.loginAccessTokenKey);
// options.headers['Authorization'] = 'Bearer $token';
options.headers['Authorization'] = 'Bearer ${'${token!}123'}';
if (checkHeaderExclusionListOfAddedParameters(options.path)) {
options.headers
.putIfAbsent(HttpHeaders.authorizationHeader, () => "Bearer $token");
}
// options.headers['Authorization'] = 'Bearer ${'${token!}123'}';
super.onRequest(options, handler);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
developer.log('Error Message: ${err.message}');
developer.log('Error res Code: ${err.response?.statusCode}');
developer.log('Error res Data: ${err.response?.data}');
developer.log('Error res status message: ${err.response?.statusMessage}');
// developer.log('Error Message: ${err.message}');
// developer.log('Error res Code: ${err.response?.statusCode}');
// developer.log('Error res Data: ${err.response?.data}');
// developer.log('Error res status message: ${err.response?.statusMessage}');
ServerFailure failure = ServerFailure.fromDioError(err);
CustomSnackBar.displaySnackBar(failure.toString());
@ -39,8 +54,8 @@ class HTTPInterceptor extends InterceptorsWrapper {
if (err.response?.statusCode == 401 && token != null) {
await AuthCubit.get(NavigationService.navigatorKey.currentContext!)
.logout();
super.onError(err, handler);
}
super.onError(err, handler);
}
/// Validates the response and returns true if it is successful (status code 2xx).
@ -60,4 +75,15 @@ class HTTPInterceptor extends InterceptorsWrapper {
return false;
}
}
checkHeaderExclusionListOfAddedParameters(String path) {
bool shouldAddHeader = true;
for (var urlConstant in headerExclusionListOfAddedParameters) {
if (path.contains(urlConstant)) {
shouldAddHeader = false;
}
}
return shouldAddHeader;
}
}

View File

@ -57,8 +57,6 @@ class HTTPService {
queryParameters: queryParameters,
options: options,
);
// developer.log("status code is ${response.statusCode}");
// developer.log("response data is ${response.data}");
return expectedResponseModel(response.data);
} catch (error) {
rethrow;

View File

@ -12,47 +12,39 @@ class SpacesAPI {
static Future<List<SpaceModel>> getSpaces() async {
var uuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
final response = await _httpService.get(
path: ApiEndpoints.spaces,
queryParameters: {
"userUuid": uuid,
},
showServerMessage: false,
expectedResponseModel: (json) => SpaceModel.fromJsonList(json),
);
return response;
try {
final response = await _httpService.get(
path: ApiEndpoints.spaces,
queryParameters: {
"userUuid": uuid,
},
showServerMessage: false,
expectedResponseModel: (json) => SpaceModel.fromJsonList(json),
);
return response;
} catch (e) {
rethrow;
}
}
//get rooms by space id
static Future<List<RoomModel>> getRoomsBySpaceId(int spaceId) async {
final response = await _httpService.get(
path: ApiEndpoints.rooms,
queryParameters: {"homeId": spaceId},
showServerMessage: false,
expectedResponseModel: (json) {
List<RoomModel> rooms = [];
for (var room in json) {
rooms.add(RoomModel.fromJson(room));
}
return rooms;
},
);
return response;
}
static Future<List<DeviceModel>> getDevicesByRoomId(int roomId) async {
final response = await _httpService.get(
path: ApiEndpoints.devicesByRoom,
queryParameters: {"roomId": roomId, "pageSize": 10},
showServerMessage: false,
expectedResponseModel: (json) {
List<DeviceModel> devices = [];
for (var device in json['devices']) {
devices.add(DeviceModel.fromJson(device));
}
return devices;
},
);
return response;
try {
final response = await _httpService.get(
path: ApiEndpoints.rooms,
queryParameters: {"homeId": spaceId},
showServerMessage: false,
expectedResponseModel: (json) {
List<RoomModel> rooms = [];
for (var room in json) {
rooms.add(RoomModel.fromJson(room));
}
return rooms;
},
);
return response;
} catch (e) {
rethrow;
}
}
}