From df13c66b1a2828a55e5d395f19fe71f406bb4fc1 Mon Sep 17 00:00:00 2001 From: Mohammad Salameh Date: Mon, 15 Apr 2024 15:47:13 +0300 Subject: [PATCH] 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. --- lib/features/app_layout/bloc/home_cubit.dart | 42 ++++---- lib/features/app_layout/bloc/home_state.dart | 38 ++++--- lib/features/auth/bloc/auth_cubit.dart | 48 ++++----- lib/features/devices/bloc/devices_cubit.dart | 104 ++++++++++--------- lib/features/scene/bloc/scene_cubit.dart | 6 +- lib/services/api/devices_api.dart | 86 ++++++++++----- lib/services/api/http_interceptor.dart | 44 ++++++-- lib/services/api/http_service.dart | 2 - lib/services/api/spaces_api.dart | 68 ++++++------ 9 files changed, 247 insertions(+), 191 deletions(-) diff --git a/lib/features/app_layout/bloc/home_cubit.dart b/lib/features/app_layout/bloc/home_cubit.dart index 73bbe88..7ea5011 100644 --- a/lib/features/app_layout/bloc/home_cubit.dart +++ b/lib/features/app_layout/bloc/home_cubit.dart @@ -125,14 +125,17 @@ class HomeCubit extends Cubit { 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 { 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 { ), ], 'Devices': [ - // IconButton( - // icon: Image.asset( - // Assets.iconsFilter, - // height: 20, - // width: 20, - // ), - // onPressed: () {}, - // ), IconButton( icon: const Icon( Icons.add, diff --git a/lib/features/app_layout/bloc/home_state.dart b/lib/features/app_layout/bloc/home_state.dart index d1f5b7d..9c20d4e 100644 --- a/lib/features/app_layout/bloc/home_state.dart +++ b/lib/features/app_layout/bloc/home_state.dart @@ -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 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 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; diff --git a/lib/features/auth/bloc/auth_cubit.dart b/lib/features/auth/bloc/auth_cubit.dart index 2ef1173..9e46276 100644 --- a/lib/features/auth/bloc/auth_cubit.dart +++ b/lib/features/auth/bloc/auth_cubit.dart @@ -41,7 +41,7 @@ class AuthCubit extends Cubit { static UserModel? user; static Token token = Token.emptyConstructor(); - +/////////////////////////////////////API CALLS///////////////////////////////////// login() async { emit(AuthLoginLoading()); try { @@ -51,23 +51,23 @@ class AuthCubit extends Cubit { 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 { 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 { 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); - } } diff --git a/lib/features/devices/bloc/devices_cubit.dart b/lib/features/devices/bloc/devices_cubit.dart index d976f59..5784091 100644 --- a/lib/features/devices/bloc/devices_cubit.dart +++ b/lib/features/devices/bloc/devices_cubit.dart @@ -251,32 +251,42 @@ class DevicesCubit extends Cubit { 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 { .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 statuses = []; try { - emitSafe(GetDeviceStatusLoading(code: code)); - int deviceIndex = HomeCubit.getInstance() - .selectedSpace! - .rooms![roomIndex] - .devices! - .indexWhere((element) => element.id == deviceId); - - List 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 diff --git a/lib/features/scene/bloc/scene_cubit.dart b/lib/features/scene/bloc/scene_cubit.dart index 350173a..f22d3e4 100644 --- a/lib/features/scene/bloc/scene_cubit.dart +++ b/lib/features/scene/bloc/scene_cubit.dart @@ -12,10 +12,8 @@ class SceneCubit extends Cubit { 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 scenes = []; diff --git a/lib/services/api/devices_api.dart b/lib/services/api/devices_api.dart index 8968bad..31bd309 100644 --- a/lib/services/api/devices_api.dart +++ b/lib/services/api/devices_api.dart @@ -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> 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> 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> 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> getDevicesByRoomId(int roomId) async { + try { + final response = await _httpService.get( + path: ApiEndpoints.devicesByRoom, + queryParameters: {"roomId": roomId, "pageSize": 10}, + showServerMessage: false, + expectedResponseModel: (json) { + List devices = []; + for (var device in json['devices']) { + devices.add(DeviceModel.fromJson(device)); + } + return devices; + }, + ); + return response; + } catch (e) { + rethrow; + } } } diff --git a/lib/services/api/http_interceptor.dart b/lib/services/api/http_interceptor.dart index 38b43cb..baae847 100644 --- a/lib/services/api/http_interceptor.dart +++ b/lib/services/api/http_interceptor.dart @@ -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 headerExclusionList = []; + + List 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; + } } diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart index 690c11c..414741d 100644 --- a/lib/services/api/http_service.dart +++ b/lib/services/api/http_service.dart @@ -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; diff --git a/lib/services/api/spaces_api.dart b/lib/services/api/spaces_api.dart index ebf2597..f05df9f 100644 --- a/lib/services/api/spaces_api.dart +++ b/lib/services/api/spaces_api.dart @@ -12,47 +12,39 @@ class SpacesAPI { static Future> 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> getRoomsBySpaceId(int spaceId) async { - final response = await _httpService.get( - path: ApiEndpoints.rooms, - queryParameters: {"homeId": spaceId}, - showServerMessage: false, - expectedResponseModel: (json) { - List rooms = []; - for (var room in json) { - rooms.add(RoomModel.fromJson(room)); - } - return rooms; - }, - ); - return response; - } - - static Future> getDevicesByRoomId(int roomId) async { - final response = await _httpService.get( - path: ApiEndpoints.devicesByRoom, - queryParameters: {"roomId": roomId, "pageSize": 10}, - showServerMessage: false, - expectedResponseModel: (json) { - List 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 rooms = []; + for (var room in json) { + rooms.add(RoomModel.fromJson(room)); + } + return rooms; + }, + ); + return response; + } catch (e) { + rethrow; + } } }