Skip to content

Commit 5146ee5

Browse files
authored
feat: implemented error handling (#44)
* feat: implemented error handling * refactor: ResponseError implements Exception instead of Error
1 parent 4ea0a12 commit 5146ee5

File tree

11 files changed

+4605
-96
lines changed

11 files changed

+4605
-96
lines changed

analysis_options.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ linter:
7373

7474

7575
analyzer:
76+
errors:
77+
# Ignore invalid annotations since freezed annotation and json serializable required it
78+
invalid_annotation_target: ignore
7679
exclude:
7780
# Ignore warnings in files from json_serializable, built_value and most generators
7881
- "**/*.g.dart"

lib/data/interceptor/auth_interceptor.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import 'package:flutter_template/data/model/auth/auth_tokens.dart';
55
import 'package:flutter_template/data/preferences/auth_preferences.dart';
66
import 'package:flutter_template/data/services/http_client/dio_http_client.dart';
77
import 'package:flutter_template/data/services/http_client/http_client.dart';
8-
import 'package:flutter_template/data/services/response_errors.dart';
8+
import 'package:flutter_template/data/services/response_error.dart';
99
import 'package:flutter_template/data/services/response_objects/tokens_response.dart';
1010
import 'package:flutter_template/domain/preferences/user_preferences.dart';
1111

@@ -80,7 +80,7 @@ class AuthInterceptor extends InterceptorsWrapper {
8080
try {
8181
final tokens = await _getNewTokens(requestOptions);
8282
await _saveTokensAndRetryCurrentRequest(tokens, requestOptions, handler);
83-
} on ResponseErrors {
83+
} on ResponseError {
8484
_tokenRefreshState = _TokenRefreshStatus.expired;
8585
await _clearUserSessionAndNotifyCallback();
8686
} on DioError catch (e) {
@@ -121,13 +121,13 @@ class AuthInterceptor extends InterceptorsWrapper {
121121
);
122122

123123
if (response == null) {
124-
throw const ResponseErrors.unprocessableEntity();
124+
throw const ResponseError.unprocessableEntity();
125125
}
126126

127127
return TokensResponse.fromJson(response['data']).getEntity();
128128
} on DioError catch (e) {
129129
if (e.response?.statusCode == 401) {
130-
throw const ResponseErrors.unauthorized();
130+
throw const ResponseError.unauthorized();
131131
}
132132
rethrow;
133133
}

lib/data/services/http_client/dio_http_client.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import 'package:dio/dio.dart';
22
import 'package:flutter_template/data/services/http_client/http_client.dart';
3-
import 'package:flutter_template/data/services/response_errors.dart';
3+
import 'package:flutter_template/data/services/response_error.dart';
44

55
/// Abstraction of the Dio http client class.
66
class DioHttpClient extends HttpClient {
@@ -42,7 +42,7 @@ class DioHttpClient extends HttpClient {
4242
);
4343
return response.data;
4444
} catch (e) {
45-
throw ResponseErrors.fromDioError(e);
45+
throw ResponseError.from(e);
4646
}
4747
}
4848

@@ -74,7 +74,7 @@ class DioHttpClient extends HttpClient {
7474
);
7575
return response.data;
7676
} catch (e) {
77-
throw ResponseErrors.fromDioError(e);
77+
throw ResponseError.from(e);
7878
}
7979
}
8080

@@ -107,15 +107,15 @@ class DioHttpClient extends HttpClient {
107107
return response.data;
108108
} catch (e) {
109109
// ignore: only_throw_errors
110-
throw ResponseErrors.fromDioError(e);
110+
throw ResponseError.from(e);
111111
}
112112
}
113113

114114
Future<Response<T>> fetch<T>(RequestOptions options) async {
115115
try {
116116
return _dio.fetch<T>(options);
117117
} catch (e) {
118-
throw ResponseErrors.fromDioError(e);
118+
throw ResponseError.from(e);
119119
}
120120
}
121121
}

lib/data/services/response_error.dart

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import 'dart:io';
2+
3+
import 'package:dio/dio.dart';
4+
import 'package:flutter/foundation.dart';
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_template/data/services/response_objects/error_response.dart';
7+
import 'package:flutter_template/nstack/nstack.dart';
8+
import 'package:freezed_annotation/freezed_annotation.dart';
9+
10+
part 'response_error.freezed.dart';
11+
12+
/// A representation of all possible errors while connecting with the
13+
/// backend.
14+
///
15+
/// We return those errors to get localized messages to display to the user.
16+
@freezed
17+
class ResponseError<T> with _$ResponseError<T> implements Exception {
18+
const ResponseError._();
19+
const factory ResponseError.noInternetConnection() = _NoInternetConnection;
20+
const factory ResponseError.sendTimeout() = _SendTimeout;
21+
const factory ResponseError.connectTimeout() = _ConnectTimeout;
22+
const factory ResponseError.receiveTimeout() = _ReceiveTimeout;
23+
const factory ResponseError.badRequest(ErrorName errorName) = _BadRequest;
24+
const factory ResponseError.notFound() = _NotFound;
25+
const factory ResponseError.tooManyRequests() = _TooManyRequests;
26+
const factory ResponseError.unprocessableEntity() = _UnprocessableEntity;
27+
const factory ResponseError.internalServerError() = _InternalServerError;
28+
const factory ResponseError.unexpectedError() = _UnexpectedError;
29+
const factory ResponseError.requestCancelled() = _RequestCancelled;
30+
const factory ResponseError.conflict() = _Conflict;
31+
const factory ResponseError.unauthorized() = _Unauthorized;
32+
const factory ResponseError.invalidPassword() = _InvalidPasswordError;
33+
const factory ResponseError.invalidEmail() = _InvalidEmailError;
34+
const factory ResponseError.invalidLoginCredentials() =
35+
_InvalidLoginCredentials;
36+
const factory ResponseError.invalidSearhTerm() = _InvalidSearchTermError;
37+
38+
static ResponseError from(Object error) {
39+
if (error is ResponseError) {
40+
return error;
41+
} else if (error is SocketException) {
42+
return const ResponseError.noInternetConnection();
43+
} else if (error is DioError) {
44+
switch (error.type) {
45+
case DioErrorType.sendTimeout:
46+
return const ResponseError.sendTimeout();
47+
case DioErrorType.connectTimeout:
48+
return const ResponseError.connectTimeout();
49+
case DioErrorType.receiveTimeout:
50+
return const ResponseError.receiveTimeout();
51+
case DioErrorType.other:
52+
return const ResponseError.noInternetConnection();
53+
case DioErrorType.cancel:
54+
return const ResponseError.requestCancelled();
55+
case DioErrorType.response:
56+
switch (error.response!.statusCode) {
57+
case 400:
58+
return ErrorResponse.fromJson(error.response!.data)
59+
.getResponseErrorType();
60+
// Returned when login credentials are invalid.
61+
case 401:
62+
return const ResponseError.unauthorized();
63+
case 404:
64+
return const ResponseError.notFound();
65+
case 409:
66+
return const ResponseError.conflict();
67+
case 422:
68+
return const ResponseError.unprocessableEntity();
69+
case 429:
70+
return const ResponseError.tooManyRequests();
71+
case 500:
72+
case 502:
73+
return const ResponseError.internalServerError();
74+
default:
75+
// If we receive a unexpected status code, we throw an exception.
76+
final statusCode = error.response!.statusCode;
77+
throw Exception('Received invalid status code: $statusCode');
78+
}
79+
}
80+
} else if (error is TypeError) {
81+
debugPrint(error.toString());
82+
}
83+
return const ResponseError.unexpectedError();
84+
}
85+
}
86+
87+
extension ResponseErrorExtensions on ResponseError {
88+
String getErrorMessage(BuildContext context) {
89+
final _localization = context.localization.error;
90+
91+
//TODO: create error module for errors and set value accordingly
92+
return when<String>(
93+
noInternetConnection: () => _localization.connectionError,
94+
sendTimeout: () => _localization.authenticationError,
95+
connectTimeout: () => _localization.authenticationError,
96+
receiveTimeout: () => _localization.authenticationError,
97+
badRequest: (message) => message.getErrorMessage(context),
98+
notFound: () => _localization.authenticationError,
99+
tooManyRequests: () => _localization.authenticationError,
100+
unprocessableEntity: () => _localization.authenticationError,
101+
internalServerError: () => _localization.authenticationError,
102+
unexpectedError: () => _localization.authenticationError,
103+
requestCancelled: () => _localization.authenticationError,
104+
conflict: () => _localization.authenticationError,
105+
unauthorized: () => _localization.authenticationError,
106+
invalidPassword: () => _localization.authenticationError,
107+
invalidEmail: () => _localization.authenticationError,
108+
invalidSearhTerm: () => _localization.authenticationError,
109+
invalidLoginCredentials: () => _localization.authenticationError,
110+
);
111+
}
112+
}

0 commit comments

Comments
 (0)