Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 6.4.0

- Added proper use of exceptions for all auth methods.
- Added the ability to add translations for all auth exceptions including two standard provided languages (nl, en)

## 6.3.2

- Fixed infinite loading when closing the forgotPasswordSucces screen and ForgotPasswordUnsuccessfull screen.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class FirebaseUserRepository implements UserRepositoryInterface {
final String userCollecton;

@override
Future<LoginResponse> loginWithEmailAndPassword({
Future<AuthResponse> loginWithEmailAndPassword({
required String email,
required String password,
}) async {
Expand All @@ -22,24 +22,16 @@ class FirebaseUserRepository implements UserRepositoryInterface {
password: password,
);

return LoginResponse(
loginSuccessful: true,
return AuthResponse(
userObject: userCredential.user,
);
} on FirebaseAuthException catch (e) {
return LoginResponse(
loginSuccessful: false,
userObject: null,
loginError: UserError(
title: e.code,
message: e.message ?? "An error occurred",
),
);
throw _mapFirebaseAuthException(e);
}
}

@override
Future<RegistrationResponse> register({
Future<AuthResponse> register({
required Map<String, dynamic> values,
}) async {
try {
Expand All @@ -54,19 +46,11 @@ class FirebaseUserRepository implements UserRepositoryInterface {
.doc(userCredential.user!.uid)
.set(values);

return RegistrationResponse(
registrationSuccessful: true,
return AuthResponse(
userObject: userCredential.user,
);
} on FirebaseAuthException catch (e) {
return RegistrationResponse(
registrationSuccessful: false,
userObject: null,
registrationError: UserError(
title: e.code,
message: e.message ?? "An error occurred",
),
);
throw _mapFirebaseAuthException(e);
}
}

Expand All @@ -80,13 +64,7 @@ class FirebaseUserRepository implements UserRepositoryInterface {
requestSuccesfull: true,
);
} on FirebaseAuthException catch (e) {
return RequestPasswordResponse(
requestSuccesfull: false,
requestPasswordError: UserError(
title: e.code,
message: e.message ?? "An error occurred",
),
);
throw _mapFirebaseAuthException(e);
}
}

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

@override
Future<bool> isLoggedIn() async => _firebaseAuth.currentUser != null;

/// Maps a [FirebaseAuthException] to a custom [AuthException].
AuthException _mapFirebaseAuthException(FirebaseAuthException e) =>
switch (e.code) {
"invalid-email" => InvalidEmailError(code: e.code, message: e.message),
"user-disabled" => UserDisabledError(code: e.code, message: e.message),
"user-not-found" => UserNotFoundError(code: e.code, message: e.message),
"wrong-password" =>
WrongPasswordError(code: e.code, message: e.message),
"email-already-in-use" =>
EmailAlreadyInUseError(code: e.code, message: e.message),
"operation-not-allowed" =>
OperationNotAllowedError(code: e.code, message: e.message),
"weak-password" => WeakPasswordError(code: e.code, message: e.message),
"too-many-requests" =>
TooManyRequestsError(code: e.code, message: e.message),
"network-request-failed" =>
NetworkError(code: e.code, message: e.message),
"invalid-credential" =>
InvalidCredentialError(code: e.code, message: e.message),
"account-exists-with-different-credential" =>
AccountExistsWithDifferentCredentialError(
code: e.code,
message: e.message,
),
"invalid-verification-code" =>
InvalidVerificationCodeError(code: e.code, message: e.message),
"invalid-verification-id" =>
InvalidVerificationIdError(code: e.code, message: e.message),
"requires-recent-login" =>
RequiresRecentLoginError(code: e.code, message: e.message),
_ => GenericAuthError(code: e.code, message: e.message),
};
}
4 changes: 2 additions & 2 deletions packages/firebase_user_repository/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: firebase_user_repository
description: "firebase_user_repository for flutter_user package"
version: 6.3.2
version: 6.4.0
repository: https://github.com/Iconica-Development/flutter_user

publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
Expand All @@ -14,7 +14,7 @@ dependencies:
sdk: flutter
user_repository_interface:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub/
version: ^6.3.2
version: ^6.4.0
cloud_firestore: ^5.4.2
firebase_auth: ^5.3.0

Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_user/lib/flutter_user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ library flutter_user;
export "package:user_repository_interface/user_repository_interface.dart";

export "src/flutter_user_navigator_userstory.dart";
export "src/models/auth_exception_formatter.dart";
export "src/models/flutter_user_options.dart";
export "src/models/forgot_password/forgot_password_options.dart";
export "src/models/forgot_password/forgot_password_spacer_options.dart";
Expand All @@ -18,7 +19,6 @@ export "src/models/login/login_translations.dart";
export "src/models/registration/auth_action.dart";
export "src/models/registration/auth_bool_field.dart";
export "src/models/registration/auth_drop_down.dart";
export "src/models/registration/auth_exception.dart";
export "src/models/registration/auth_field.dart";
export "src/models/registration/auth_pass_field.dart";
export "src/models/registration/auth_step.dart";
Expand Down
121 changes: 60 additions & 61 deletions packages/flutter_user/lib/src/flutter_user_navigator_userstory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import "dart:async";

import "package:flutter/material.dart";
import "package:flutter_user/flutter_user.dart";
import "package:flutter_user/src/models/auth_error_details.dart";

class FlutterUserNavigatorUserstory extends StatefulWidget {
const FlutterUserNavigatorUserstory({
Expand Down Expand Up @@ -88,47 +89,45 @@ class _FlutterUserNavigatorUserstoryState
options!.loginTranslations.loginTitle,
style: theme.textTheme.headlineLarge,
);
var subtitle = Text(options!.loginTranslations.loginSubtitle ?? "");
var subtitle = Text(options?.loginTranslations.loginSubtitle ?? "");

FutureOr<void> onLogin(String email, String password) async {
await options!.beforeLogin?.call(email, password);
await options?.beforeLogin?.call(email, password);
if (!mounted) return;
unawaited(showLoadingIndicator(context));
var loginResponse = await userService!.loginWithEmailAndPassword(
email: email,
password: password,
);

if (!loginResponse.loginSuccessful) {
try {
await userService?.loginWithEmailAndPassword(
email: email,
password: password,
);
} on AuthException catch (e) {
if (!mounted) return;
Navigator.of(context, rootNavigator: true).pop();
if (!context.mounted) return;
await errorScaffoldMessenger(context, loginResponse);
var authErrorDetails = options!.authExceptionFormatter.format(e);
await errorScaffoldMessenger(context, authErrorDetails);
return;
}
await options!.afterLogin?.call();

if (loginResponse.loginSuccessful) {
var onboardingUser = await options!.onBoardedUser?.call();
if (!mounted) return;
Navigator.of(context, rootNavigator: true).pop();
if (options!.useOnboarding && onboardingUser?.onboarded == false) {
await push(
Onboarding(
onboardingFinished: (results) async {
await options!.onOnboardingComplete?.call(results);
if (!mounted || !context.mounted) return;
Navigator.of(context).pop();
await pushReplacement(widget.afterLoginScreen);
},
),
);
} else {
if (!context.mounted) {
return;
}
await pushReplacement(widget.afterLoginScreen);
}
await options?.afterLogin?.call();

var onboardingUser = await options?.onBoardedUser?.call();
if (!mounted) return;
Navigator.of(context, rootNavigator: true).pop();
if (options!.useOnboarding && onboardingUser?.onboarded == false) {
await push(
Onboarding(
onboardingFinished: (results) async {
await options?.onOnboardingComplete?.call(results);
if (!mounted || !context.mounted) return;
Navigator.of(context).pop();
await pushReplacement(widget.afterLoginScreen);
},
),
);
} else {
if (!context.mounted) return;
await pushReplacement(widget.afterLoginScreen);
}
}

Expand All @@ -138,11 +137,11 @@ class _FlutterUserNavigatorUserstoryState
options: options!.loginOptions,
onLogin: onLogin,
onForgotPassword: (email, ctx) async {
await options!.onForgotPassword?.call(email, ctx) ??
await options?.onForgotPassword?.call(email, ctx) ??
await push(_forgotPasswordScreen());
},
onRegister: (email, password, context) async {
await options!.onRegister?.call(email, password, context) ??
await options?.onRegister?.call(email, password, context) ??
await push(_registrationScreen());
},
);
Expand All @@ -164,24 +163,27 @@ class _FlutterUserNavigatorUserstoryState
);

FutureOr<void> onRequestForgotPassword(String email) async {
if (options!.onRequestForgotPassword != null) {
if (options?.onRequestForgotPassword != null) {
await options!.onRequestForgotPassword!(email);
return;
}
unawaited(showLoadingIndicator(context));

var requestPasswordReponse =
await userService!.requestChangePassword(email: email);

if (!mounted) return;
Navigator.of(context).pop();

if (!requestPasswordReponse.requestSuccesfull) {
try {
var response = await userService!.requestChangePassword(email: email);
if (!mounted) return;
Navigator.of(context).pop();
if (response.requestSuccesfull) {
await pushReplacement(_forgotPasswordSuccessScreen());
} else {
await push(_forgotPasswordUnsuccessfullScreen());
}
} on AuthException catch (_) {
if (!mounted) return;
Navigator.of(context).pop();
await push(_forgotPasswordUnsuccessfullScreen());
return;
}

await pushReplacement(_forgotPasswordSuccessScreen());
}

return ForgotPasswordForm(
Expand All @@ -196,7 +198,7 @@ class _FlutterUserNavigatorUserstoryState
Widget _forgotPasswordSuccessScreen() => ForgotPasswordSuccess(
translations: options!.forgotPasswordTranslations,
onRequestForgotPassword: () async {
await options!.onForgotPasswordSuccess?.call() ??
await options?.onForgotPasswordSuccess?.call() ??
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
},
Expand All @@ -205,7 +207,7 @@ class _FlutterUserNavigatorUserstoryState
Widget _forgotPasswordUnsuccessfullScreen() => ForgotPasswordUnsuccessfull(
translations: forgotPasswordTranslations!,
onPressed: () async {
await options!.onForgotPasswordUnsuccessful?.call() ??
await options?.onForgotPasswordUnsuccessful?.call() ??
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
},
Expand All @@ -215,50 +217,47 @@ class _FlutterUserNavigatorUserstoryState
registrationOptions: registrationOptions!,
userService: userService!,
onError: (error) async {
if (options!.onRegistrationError != null) {
return options!
.onRegistrationError!(error ?? "Something went wrong");
var errorDetails = options!.authExceptionFormatter.format(error);

if (options?.onRegistrationError != null) {
return options!.onRegistrationError!(error, errorDetails);
}
await push(
_registrationUnsuccessfullScreen(
error ?? "Something went wrong",
errorDetails,
),
);
var isPasswordError = error?.contains("weak-password") ?? false;
var isEmailError = error?.contains("email-already-in-use") ?? false;
if (isPasswordError) {
return 1;
}
if (isEmailError) {
return 0;
}

if (error is WeakPasswordError) return 1;

if (error is EmailAlreadyInUseError) return 0;

return null;
},
afterRegistration: () async {
options!.afterRegistration?.call() ??
options?.afterRegistration?.call() ??
await pushReplacement(_registrationSuccessScreen());
},
);

Widget _registrationSuccessScreen() => RegistrationSuccess(
registrationOptions: registrationOptions!,
onPressed: () async {
await options!.afterRegistrationSuccess?.call() ??
await options?.afterRegistrationSuccess?.call() ??
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
},
);

Widget _registrationUnsuccessfullScreen(String error) =>
Widget _registrationUnsuccessfullScreen(AuthErrorDetails errorDetails) =>
RegistrationUnsuccessfull(
registrationOptions: registrationOptions!,
onPressed: () async {
await options!.afterRegistrationUnsuccessful?.call() ??
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
},
error: error,
errorDetails: errorDetails,
);

Future<void> push(Widget screen) async {
Expand Down
9 changes: 9 additions & 0 deletions packages/flutter_user/lib/src/models/auth_error_details.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class AuthErrorDetails {
const AuthErrorDetails({
required this.title,
required this.message,
});

final String title;
final String message;
}
Loading