Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error handling #13

Merged
merged 10 commits into from
May 1, 2024
79 changes: 79 additions & 0 deletions data/lib/errors/app_error.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:data/errors/app_error_l10n_codes.dart';
import 'package:firebase_auth/firebase_auth.dart';

import '../utils/constant/firebase_error_constant.dart';

class AppError implements Exception {
final String? message;
final String? l10nCode;
final String? statusCode;
final StackTrace? stackTrace;

const AppError({
this.message,
this.statusCode,
this.l10nCode,
this.stackTrace,
});

@override
String toString() {
return '$runtimeType{message: $message, code: $statusCode, l10nCode: $l10nCode, stackTrace: $stackTrace}';
}

factory AppError.fromError(Object error, [StackTrace? stack]) {
if (error is AppError) {
return error;
} else if (error is SocketException) {
return const NoConnectionError();
} else if (error is FirebaseException) {
return _handleFirebaseError(error);
} else if (error is TypeError) {
return SomethingWentWrongError(stackTrace: error.stackTrace);
} else {
return SomethingWentWrongError(stackTrace: stack);
}
}

static AppError _handleFirebaseError(FirebaseException error) {
switch (error.code) {
case errorInvalidVerificationCode:
return AppError(
statusCode: error.code,
message: error.message,
l10nCode: AppErrorL10nCodes.invalidVerificationCode,
stackTrace: error.stackTrace);
case errorInvalidPhoneNumber:
return AppError(
statusCode: error.code,
message: error.message,
l10nCode: AppErrorL10nCodes.invalidPhoneNumber,
stackTrace: error.stackTrace);
case errorNetworkRequestFailed:
return const NoConnectionError();
default:
return SomethingWentWrongError(
statusCode: error.code,
message: error.message,
stackTrace: error.stackTrace);
}
}
}

class NoConnectionError extends AppError {
const NoConnectionError()
: super(
l10nCode: AppErrorL10nCodes.noInternetConnection,
message:
"No internet connection. Please check your network and try again.");
}

class SomethingWentWrongError extends AppError {
const SomethingWentWrongError({
super.message,
super.statusCode,
super.stackTrace,
}) : super(l10nCode: AppErrorL10nCodes.somethingWentWrongError);
}
6 changes: 6 additions & 0 deletions data/lib/errors/app_error_l10n_codes.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AppErrorL10nCodes {
static const noInternetConnection = 'no-internet-connection';
static const somethingWentWrongError = 'something-went-wrong';
static const invalidVerificationCode = 'invalid-verification-code';
static const invalidPhoneNumber = 'invalid-phone-number';
}
133 changes: 83 additions & 50 deletions data/lib/service/auth/auth_service.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:data/errors/app_error.dart';
import 'package:data/extensions/string_extensions.dart';
import 'package:data/service/user/user_service.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
Expand All @@ -17,77 +19,108 @@ class AuthService {
Stream<User?> get user => _auth.authStateChanges();

Future<void> verifyPhoneNumber({
required String countryCode,
required String phoneNumber,
Function(String, int?)? onCodeSent,
Function(PhoneAuthCredential, UserCredential)? onVerificationCompleted,
Function(FirebaseAuthException)? onVerificationFailed,
Function(AppError)? onVerificationFailed,
Function(String)? onCodeAutoRetrievalTimeout,
}) async {
await _auth.verifyPhoneNumber(
phoneNumber: phoneNumber,
verificationCompleted: (phoneAuthCredential) async {
final userCredential =
await _auth.signInWithCredential(phoneAuthCredential);
_onVerificationSuccess(userCredential);
onVerificationCompleted != null
? onVerificationCompleted(phoneAuthCredential, userCredential)
: null;
},
verificationFailed: (FirebaseAuthException e) =>
onVerificationFailed != null ? onVerificationFailed(e) : null,
codeSent: (String verificationId, int? resendToken) =>
onCodeSent != null ? onCodeSent(verificationId, resendToken) : null,
codeAutoRetrievalTimeout: (verificationId) =>
onCodeAutoRetrievalTimeout != null
? onCodeAutoRetrievalTimeout(verificationId)
: null,
);
try {
await _auth.verifyPhoneNumber(
phoneNumber: countryCode + phoneNumber,
verificationCompleted: (phoneAuthCredential) async {
final userCredential =
await _auth.signInWithCredential(phoneAuthCredential);
_onVerificationSuccess(countryCode, phoneNumber, userCredential);
onVerificationCompleted != null
? onVerificationCompleted(phoneAuthCredential, userCredential)
: null;
},
verificationFailed: (FirebaseAuthException e) =>
onVerificationFailed != null
? onVerificationFailed(AppError.fromError(e, e.stackTrace))
: null,
codeSent: (String verificationId, int? resendToken) =>
onCodeSent != null ? onCodeSent(verificationId, resendToken) : null,
codeAutoRetrievalTimeout: (verificationId) =>
onCodeAutoRetrievalTimeout != null
? onCodeAutoRetrievalTimeout(verificationId)
: null,
);
} catch (error, stack) {
throw AppError.fromError(error, stack);
}
Comment on lines +29 to +53
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure comprehensive error handling in verifyPhoneNumber.

The method verifyPhoneNumber uses a try-catch block to handle errors, which is good. However, ensure that all potential exceptions, especially those specific to phone number verification, are adequately handled and logged.

}

Future<void> verifyOTP(String verificationId, String otp) async {
final credential = PhoneAuthProvider.credential(
verificationId: verificationId,
smsCode: otp,
);
Future<void> verifyOTP(String countryCode, String phoneNumber,
String verificationId, String otp) async {
try {
final credential = PhoneAuthProvider.credential(
verificationId: verificationId,
smsCode: otp,
);

final userCredential = await _auth.signInWithCredential(credential);
await _onVerificationSuccess(userCredential);
final userCredential = await _auth.signInWithCredential(credential);
await _onVerificationSuccess(countryCode, phoneNumber, userCredential);
} catch (error, stack) {
throw AppError.fromError(error, stack);
}
}

Future<void> _onVerificationSuccess(UserCredential credential) async {
if (credential.additionalUserInfo?.isNewUser ?? false) {
if (_auth.currentUser == null) {
return;
}
UserModel user = UserModel(
id: _auth.currentUser!.uid,
phone: _auth.currentUser!.phoneNumber,
created_at: DateTime.now());
await _userService.updateUser(user);
} else {
final uid = credential.user?.uid;
if (uid == null) {
return;
Future<void> _onVerificationSuccess(
String countryCode, String phoneNumber, UserCredential credential) async {
try {
if (credential.additionalUserInfo?.isNewUser ?? false) {
if (_auth.currentUser == null) {
return;
}
final phone = "$countryCode ${phoneNumber.caseAndSpaceInsensitive}";
UserModel user = UserModel(
id: _auth.currentUser!.uid,
phone: phone,
created_at: DateTime.now());
await _userService.updateUser(user);
} else {
final uid = credential.user?.uid;
if (uid == null) {
return;
}
await _userService.getUser(uid);
}
await _userService.getUser(uid);
} catch (error, stack) {
throw AppError.fromError(error, stack);
}
}

Future<void> reauthenticateAndDeleteAccount() async {
final providerData = _auth.currentUser?.providerData.first;
if (PhoneAuthProvider().providerId == providerData?.providerId) {
await _auth.currentUser?.reauthenticateWithProvider(PhoneAuthProvider());
deleteAccount();
try {
final providerData = _auth.currentUser?.providerData.first;
if (PhoneAuthProvider().providerId == providerData?.providerId) {
await _auth.currentUser
?.reauthenticateWithProvider(PhoneAuthProvider());
deleteAccount();
}
} catch (error, stack) {
throw AppError.fromError(error, stack);
}
}

Future<void> signOut() async {
await _auth.signOut();
_userService.signOutUser();
try {
await _auth.signOut();
_userService.signOutUser();
} catch (error, stack) {
throw AppError.fromError(error, stack);
}
}

Future<void> deleteAccount() async {
await _auth.currentUser?.delete();
await _userService.deleteUser();
try {
await _auth.currentUser?.delete();
await _userService.deleteUser();
} catch (error, stack) {
throw AppError.fromError(error, stack);
}
}
}
92 changes: 54 additions & 38 deletions data/lib/service/ball_score/ball_score_service.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:data/api/ball_score/ball_score_model.dart';
import 'package:data/errors/app_error.dart';
import 'package:data/storage/app_preferences.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

Expand All @@ -24,32 +24,40 @@ class BallScoreService {
BallScoreService(this._firestore, this._currentUserId);

Future<String> updateBallScore(BallScoreModel score) async {
DocumentReference scoreRef =
_firestore.collection(_collectionName).doc(score.id);
WriteBatch batch = _firestore.batch();
try {
DocumentReference scoreRef =
_firestore.collection(_collectionName).doc(score.id);
WriteBatch batch = _firestore.batch();

batch.set(scoreRef, score.toJson(), SetOptions(merge: true));
String newScoreId = scoreRef.id;
batch.set(scoreRef, score.toJson(), SetOptions(merge: true));
String newScoreId = scoreRef.id;

if (score.id == null) {
batch.update(scoreRef, {'id': newScoreId});
}
if (score.id == null) {
batch.update(scoreRef, {'id': newScoreId});
}

await batch.commit();
return newScoreId;
await batch.commit();
return newScoreId;
} catch (error, stack) {
throw AppError.fromError(error, stack);
}
}

Future<List<BallScoreModel>> getBallScoreListByInningId(
String inningId) async {
final QuerySnapshot<Map<String, dynamic>> snapshot = await _firestore
.collection(_collectionName)
.where('inning_id', isEqualTo: inningId)
.get();

return snapshot.docs.map((doc) {
final data = doc.data();
return BallScoreModel.fromJson(data).copyWith(id: doc.id);
}).toList();
try {
final QuerySnapshot<Map<String, dynamic>> snapshot = await _firestore
.collection(_collectionName)
.where('inning_id', isEqualTo: inningId)
.get();

return snapshot.docs.map((doc) {
final data = doc.data();
return BallScoreModel.fromJson(data).copyWith(id: doc.id);
}).toList();
} catch (error, stack) {
cp-sidhdhi-p marked this conversation as resolved.
Show resolved Hide resolved
throw AppError.fromError(error, stack);
}
}

Stream<List<BallScoreModel>> getBallScoresStreamByInningIds(
Expand All @@ -69,11 +77,11 @@ class BallScoreService {
}).toList();

controller.add(ballScores);
} catch (error) {
controller.addError(error);
} catch (error, stack) {
controller.addError(AppError.fromError(error, stack));
}
}, onError: (error) {
controller.addError(error);
}, onError: (error, stack) {
controller.addError(AppError.fromError(error, stack));
});

return controller.stream;
Expand All @@ -83,22 +91,30 @@ class BallScoreService {
if (_currentUserId == null) {
return [];
}
final QuerySnapshot<Map<String, dynamic>> snapshot = await _firestore
.collection(_collectionName)
.where(Filter.or(
Filter("bowler_id", isEqualTo: _currentUserId),
Filter("batsman_id", isEqualTo: _currentUserId),
Filter("wicket_taker_id", isEqualTo: _currentUserId),
Filter("player_out_id", isEqualTo: _currentUserId)))
.get();

return snapshot.docs.map((doc) {
final data = doc.data();
return BallScoreModel.fromJson(data).copyWith(id: doc.id);
}).toList();
try {
final QuerySnapshot<Map<String, dynamic>> snapshot = await _firestore
.collection(_collectionName)
.where(Filter.or(
Filter("bowler_id", isEqualTo: _currentUserId),
Filter("batsman_id", isEqualTo: _currentUserId),
Filter("wicket_taker_id", isEqualTo: _currentUserId),
Filter("player_out_id", isEqualTo: _currentUserId)))
.get();

return snapshot.docs.map((doc) {
final data = doc.data();
return BallScoreModel.fromJson(data).copyWith(id: doc.id);
}).toList();
} catch (error, stack) {
throw AppError.fromError(error, stack);
}
}

Future<void> deleteBall(String ballId) async {
await _firestore.collection(_collectionName).doc(ballId).delete();
try {
await _firestore.collection(_collectionName).doc(ballId).delete();
} catch (error, stack) {
throw AppError.fromError(error, stack);
}
}
}
Loading
Loading