Skip to content

Commit b53e011

Browse files
JacquesDIconicaFreek van de Ven
authored andcommitted
feat: add the use of exceptions for UserService and implement translations for these exceptions
1 parent 6938a3c commit b53e011

29 files changed

Lines changed: 448 additions & 256 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 6.4.0
2+
3+
- Added proper use of exceptions for all auth methods.
4+
- Added the ability to add translations for all auth exceptions including two standard provided languages (nl, en)
5+
16
## 6.3.2
27

38
- Fixed infinite loading when closing the forgotPasswordSucces screen and ForgotPasswordUnsuccessfull screen.

packages/firebase_user_repository/lib/src/firebase_user_repository.dart

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class FirebaseUserRepository implements UserRepositoryInterface {
1212
final String userCollecton;
1313

1414
@override
15-
Future<LoginResponse> loginWithEmailAndPassword({
15+
Future<AuthResponse> loginWithEmailAndPassword({
1616
required String email,
1717
required String password,
1818
}) async {
@@ -22,24 +22,16 @@ class FirebaseUserRepository implements UserRepositoryInterface {
2222
password: password,
2323
);
2424

25-
return LoginResponse(
26-
loginSuccessful: true,
25+
return AuthResponse(
2726
userObject: userCredential.user,
2827
);
2928
} on FirebaseAuthException catch (e) {
30-
return LoginResponse(
31-
loginSuccessful: false,
32-
userObject: null,
33-
loginError: UserError(
34-
title: e.code,
35-
message: e.message ?? "An error occurred",
36-
),
37-
);
29+
throw _mapFirebaseAuthException(e);
3830
}
3931
}
4032

4133
@override
42-
Future<RegistrationResponse> register({
34+
Future<AuthResponse> register({
4335
required Map<String, dynamic> values,
4436
}) async {
4537
try {
@@ -54,19 +46,11 @@ class FirebaseUserRepository implements UserRepositoryInterface {
5446
.doc(userCredential.user!.uid)
5547
.set(values);
5648

57-
return RegistrationResponse(
58-
registrationSuccessful: true,
49+
return AuthResponse(
5950
userObject: userCredential.user,
6051
);
6152
} on FirebaseAuthException catch (e) {
62-
return RegistrationResponse(
63-
registrationSuccessful: false,
64-
userObject: null,
65-
registrationError: UserError(
66-
title: e.code,
67-
message: e.message ?? "An error occurred",
68-
),
69-
);
53+
throw _mapFirebaseAuthException(e);
7054
}
7155
}
7256

@@ -80,13 +64,7 @@ class FirebaseUserRepository implements UserRepositoryInterface {
8064
requestSuccesfull: true,
8165
);
8266
} on FirebaseAuthException catch (e) {
83-
return RequestPasswordResponse(
84-
requestSuccesfull: false,
85-
requestPasswordError: UserError(
86-
title: e.code,
87-
message: e.message ?? "An error occurred",
88-
),
89-
);
67+
throw _mapFirebaseAuthException(e);
9068
}
9169
}
9270

@@ -101,4 +79,37 @@ class FirebaseUserRepository implements UserRepositoryInterface {
10179

10280
@override
10381
Future<bool> isLoggedIn() async => _firebaseAuth.currentUser != null;
82+
83+
/// Maps a [FirebaseAuthException] to a custom [AuthException].
84+
AuthException _mapFirebaseAuthException(FirebaseAuthException e) =>
85+
switch (e.code) {
86+
"invalid-email" => InvalidEmailError(code: e.code, message: e.message),
87+
"user-disabled" => UserDisabledError(code: e.code, message: e.message),
88+
"user-not-found" => UserNotFoundError(code: e.code, message: e.message),
89+
"wrong-password" =>
90+
WrongPasswordError(code: e.code, message: e.message),
91+
"email-already-in-use" =>
92+
EmailAlreadyInUseError(code: e.code, message: e.message),
93+
"operation-not-allowed" =>
94+
OperationNotAllowedError(code: e.code, message: e.message),
95+
"weak-password" => WeakPasswordError(code: e.code, message: e.message),
96+
"too-many-requests" =>
97+
TooManyRequestsError(code: e.code, message: e.message),
98+
"network-request-failed" =>
99+
NetworkError(code: e.code, message: e.message),
100+
"invalid-credential" =>
101+
InvalidCredentialError(code: e.code, message: e.message),
102+
"account-exists-with-different-credential" =>
103+
AccountExistsWithDifferentCredentialError(
104+
code: e.code,
105+
message: e.message,
106+
),
107+
"invalid-verification-code" =>
108+
InvalidVerificationCodeError(code: e.code, message: e.message),
109+
"invalid-verification-id" =>
110+
InvalidVerificationIdError(code: e.code, message: e.message),
111+
"requires-recent-login" =>
112+
RequiresRecentLoginError(code: e.code, message: e.message),
113+
_ => GenericAuthError(code: e.code, message: e.message),
114+
};
104115
}

packages/firebase_user_repository/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: firebase_user_repository
22
description: "firebase_user_repository for flutter_user package"
3-
version: 6.3.2
3+
version: 6.4.0
44
repository: https://github.com/Iconica-Development/flutter_user
55

66
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
@@ -14,7 +14,7 @@ dependencies:
1414
sdk: flutter
1515
user_repository_interface:
1616
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub/
17-
version: ^6.3.2
17+
version: ^6.4.0
1818
cloud_firestore: ^5.4.2
1919
firebase_auth: ^5.3.0
2020

packages/flutter_user/lib/flutter_user.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ library flutter_user;
66
export "package:user_repository_interface/user_repository_interface.dart";
77

88
export "src/flutter_user_navigator_userstory.dart";
9+
export "src/models/auth_exception_formatter.dart";
910
export "src/models/flutter_user_options.dart";
1011
export "src/models/forgot_password/forgot_password_options.dart";
1112
export "src/models/forgot_password/forgot_password_spacer_options.dart";
@@ -18,7 +19,6 @@ export "src/models/login/login_translations.dart";
1819
export "src/models/registration/auth_action.dart";
1920
export "src/models/registration/auth_bool_field.dart";
2021
export "src/models/registration/auth_drop_down.dart";
21-
export "src/models/registration/auth_exception.dart";
2222
export "src/models/registration/auth_field.dart";
2323
export "src/models/registration/auth_pass_field.dart";
2424
export "src/models/registration/auth_step.dart";

packages/flutter_user/lib/src/flutter_user_navigator_userstory.dart

Lines changed: 60 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import "dart:async";
22

33
import "package:flutter/material.dart";
44
import "package:flutter_user/flutter_user.dart";
5+
import "package:flutter_user/src/models/auth_error_details.dart";
56

67
class FlutterUserNavigatorUserstory extends StatefulWidget {
78
const FlutterUserNavigatorUserstory({
@@ -88,47 +89,45 @@ class _FlutterUserNavigatorUserstoryState
8889
options!.loginTranslations.loginTitle,
8990
style: theme.textTheme.headlineLarge,
9091
);
91-
var subtitle = Text(options!.loginTranslations.loginSubtitle ?? "");
92+
var subtitle = Text(options?.loginTranslations.loginSubtitle ?? "");
9293

9394
FutureOr<void> onLogin(String email, String password) async {
94-
await options!.beforeLogin?.call(email, password);
95+
await options?.beforeLogin?.call(email, password);
9596
if (!mounted) return;
9697
unawaited(showLoadingIndicator(context));
97-
var loginResponse = await userService!.loginWithEmailAndPassword(
98-
email: email,
99-
password: password,
100-
);
101-
102-
if (!loginResponse.loginSuccessful) {
98+
try {
99+
await userService?.loginWithEmailAndPassword(
100+
email: email,
101+
password: password,
102+
);
103+
} on AuthException catch (e) {
103104
if (!mounted) return;
104105
Navigator.of(context, rootNavigator: true).pop();
105106
if (!context.mounted) return;
106-
await errorScaffoldMessenger(context, loginResponse);
107+
var authErrorDetails = options!.authExceptionFormatter.format(e);
108+
await errorScaffoldMessenger(context, authErrorDetails);
107109
return;
108110
}
109-
await options!.afterLogin?.call();
110111

111-
if (loginResponse.loginSuccessful) {
112-
var onboardingUser = await options!.onBoardedUser?.call();
113-
if (!mounted) return;
114-
Navigator.of(context, rootNavigator: true).pop();
115-
if (options!.useOnboarding && onboardingUser?.onboarded == false) {
116-
await push(
117-
Onboarding(
118-
onboardingFinished: (results) async {
119-
await options!.onOnboardingComplete?.call(results);
120-
if (!mounted || !context.mounted) return;
121-
Navigator.of(context).pop();
122-
await pushReplacement(widget.afterLoginScreen);
123-
},
124-
),
125-
);
126-
} else {
127-
if (!context.mounted) {
128-
return;
129-
}
130-
await pushReplacement(widget.afterLoginScreen);
131-
}
112+
await options?.afterLogin?.call();
113+
114+
var onboardingUser = await options?.onBoardedUser?.call();
115+
if (!mounted) return;
116+
Navigator.of(context, rootNavigator: true).pop();
117+
if (options!.useOnboarding && onboardingUser?.onboarded == false) {
118+
await push(
119+
Onboarding(
120+
onboardingFinished: (results) async {
121+
await options?.onOnboardingComplete?.call(results);
122+
if (!mounted || !context.mounted) return;
123+
Navigator.of(context).pop();
124+
await pushReplacement(widget.afterLoginScreen);
125+
},
126+
),
127+
);
128+
} else {
129+
if (!context.mounted) return;
130+
await pushReplacement(widget.afterLoginScreen);
132131
}
133132
}
134133

@@ -138,11 +137,11 @@ class _FlutterUserNavigatorUserstoryState
138137
options: options!.loginOptions,
139138
onLogin: onLogin,
140139
onForgotPassword: (email, ctx) async {
141-
await options!.onForgotPassword?.call(email, ctx) ??
140+
await options?.onForgotPassword?.call(email, ctx) ??
142141
await push(_forgotPasswordScreen());
143142
},
144143
onRegister: (email, password, context) async {
145-
await options!.onRegister?.call(email, password, context) ??
144+
await options?.onRegister?.call(email, password, context) ??
146145
await push(_registrationScreen());
147146
},
148147
);
@@ -164,24 +163,27 @@ class _FlutterUserNavigatorUserstoryState
164163
);
165164

166165
FutureOr<void> onRequestForgotPassword(String email) async {
167-
if (options!.onRequestForgotPassword != null) {
166+
if (options?.onRequestForgotPassword != null) {
168167
await options!.onRequestForgotPassword!(email);
169168
return;
170169
}
171170
unawaited(showLoadingIndicator(context));
172171

173-
var requestPasswordReponse =
174-
await userService!.requestChangePassword(email: email);
175-
176-
if (!mounted) return;
177-
Navigator.of(context).pop();
178-
179-
if (!requestPasswordReponse.requestSuccesfull) {
172+
try {
173+
var response = await userService!.requestChangePassword(email: email);
174+
if (!mounted) return;
175+
Navigator.of(context).pop();
176+
if (response.requestSuccesfull) {
177+
await pushReplacement(_forgotPasswordSuccessScreen());
178+
} else {
179+
await push(_forgotPasswordUnsuccessfullScreen());
180+
}
181+
} on AuthException catch (_) {
182+
if (!mounted) return;
183+
Navigator.of(context).pop();
180184
await push(_forgotPasswordUnsuccessfullScreen());
181185
return;
182186
}
183-
184-
await pushReplacement(_forgotPasswordSuccessScreen());
185187
}
186188

187189
return ForgotPasswordForm(
@@ -196,7 +198,7 @@ class _FlutterUserNavigatorUserstoryState
196198
Widget _forgotPasswordSuccessScreen() => ForgotPasswordSuccess(
197199
translations: options!.forgotPasswordTranslations,
198200
onRequestForgotPassword: () async {
199-
await options!.onForgotPasswordSuccess?.call() ??
201+
await options?.onForgotPasswordSuccess?.call() ??
200202
// ignore: use_build_context_synchronously
201203
Navigator.of(context).pop();
202204
},
@@ -205,7 +207,7 @@ class _FlutterUserNavigatorUserstoryState
205207
Widget _forgotPasswordUnsuccessfullScreen() => ForgotPasswordUnsuccessfull(
206208
translations: forgotPasswordTranslations!,
207209
onPressed: () async {
208-
await options!.onForgotPasswordUnsuccessful?.call() ??
210+
await options?.onForgotPasswordUnsuccessful?.call() ??
209211
// ignore: use_build_context_synchronously
210212
Navigator.of(context).pop();
211213
},
@@ -215,50 +217,47 @@ class _FlutterUserNavigatorUserstoryState
215217
registrationOptions: registrationOptions!,
216218
userService: userService!,
217219
onError: (error) async {
218-
if (options!.onRegistrationError != null) {
219-
return options!
220-
.onRegistrationError!(error ?? "Something went wrong");
220+
var errorDetails = options!.authExceptionFormatter.format(error);
221+
222+
if (options?.onRegistrationError != null) {
223+
return options!.onRegistrationError!(error, errorDetails);
221224
}
222225
await push(
223226
_registrationUnsuccessfullScreen(
224-
error ?? "Something went wrong",
227+
errorDetails,
225228
),
226229
);
227-
var isPasswordError = error?.contains("weak-password") ?? false;
228-
var isEmailError = error?.contains("email-already-in-use") ?? false;
229-
if (isPasswordError) {
230-
return 1;
231-
}
232-
if (isEmailError) {
233-
return 0;
234-
}
230+
231+
if (error is WeakPasswordError) return 1;
232+
233+
if (error is EmailAlreadyInUseError) return 0;
235234

236235
return null;
237236
},
238237
afterRegistration: () async {
239-
options!.afterRegistration?.call() ??
238+
options?.afterRegistration?.call() ??
240239
await pushReplacement(_registrationSuccessScreen());
241240
},
242241
);
243242

244243
Widget _registrationSuccessScreen() => RegistrationSuccess(
245244
registrationOptions: registrationOptions!,
246245
onPressed: () async {
247-
await options!.afterRegistrationSuccess?.call() ??
246+
await options?.afterRegistrationSuccess?.call() ??
248247
// ignore: use_build_context_synchronously
249248
Navigator.of(context).pop();
250249
},
251250
);
252251

253-
Widget _registrationUnsuccessfullScreen(String error) =>
252+
Widget _registrationUnsuccessfullScreen(AuthErrorDetails errorDetails) =>
254253
RegistrationUnsuccessfull(
255254
registrationOptions: registrationOptions!,
256255
onPressed: () async {
257256
await options!.afterRegistrationUnsuccessful?.call() ??
258257
// ignore: use_build_context_synchronously
259258
Navigator.of(context).pop();
260259
},
261-
error: error,
260+
errorDetails: errorDetails,
262261
);
263262

264263
Future<void> push(Widget screen) async {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class AuthErrorDetails {
2+
const AuthErrorDetails({
3+
required this.title,
4+
required this.message,
5+
});
6+
7+
final String title;
8+
final String message;
9+
}

0 commit comments

Comments
 (0)