Skip to content

Commit 67909d6

Browse files
authored
Merge pull request #571 from DevKor-github/feature/issue-544-user-repository-auth-contract
refactor: decouple UserRepository Google auth contract
2 parents 7327343 + 557ffc0 commit 67909d6

14 files changed

Lines changed: 288 additions & 181 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:google_sign_in/google_sign_in.dart';
3+
import 'package:injectable/injectable.dart';
4+
import 'package:on_time_front/core/logging/app_logger.dart';
5+
import 'package:on_time_front/domain/entities/google_auth_credential.dart';
6+
7+
abstract interface class GoogleAuthenticationService {
8+
Stream<GoogleAuthCredential> get authenticationCredentials;
9+
10+
bool get supportsAuthenticate;
11+
12+
Future<void> initialize();
13+
14+
Future<GoogleAuthCredential> authenticate();
15+
16+
Future<void> disconnect();
17+
}
18+
19+
class GoogleAuthenticationCanceledException implements Exception {
20+
const GoogleAuthenticationCanceledException();
21+
}
22+
23+
@Singleton(as: GoogleAuthenticationService)
24+
class GoogleSignInAuthenticationService implements GoogleAuthenticationService {
25+
GoogleSignInAuthenticationService({@ignoreParam GoogleSignIn? googleSignIn})
26+
: _googleSignIn = googleSignIn ?? GoogleSignIn.instance;
27+
28+
static const _googleIosClientId =
29+
'456571312261-r35ah9qi0qaq7al007e2db0e0jmjcmb4.apps.googleusercontent.com';
30+
static const _googleServerClientId =
31+
'456571312261-5kuf2r6i5i7lqjr7qealv06sdgkn3hcp.apps.googleusercontent.com';
32+
static const _googleScopes = ['email', 'profile'];
33+
34+
final GoogleSignIn _googleSignIn;
35+
Future<void>? _initialization;
36+
37+
@override
38+
Stream<GoogleAuthCredential> get authenticationCredentials => _googleSignIn
39+
.authenticationEvents
40+
.where((event) => event is GoogleSignInAuthenticationEventSignIn)
41+
.cast<GoogleSignInAuthenticationEventSignIn>()
42+
.map((event) => _credentialFromAccount(event.user));
43+
44+
@override
45+
bool get supportsAuthenticate => _googleSignIn.supportsAuthenticate();
46+
47+
@override
48+
Future<void> initialize() {
49+
return _initialization ??= _initialize();
50+
}
51+
52+
Future<void> _initialize() async {
53+
await _googleSignIn.initialize(
54+
clientId: _googleClientId,
55+
serverClientId: _googleServerClientId,
56+
);
57+
}
58+
59+
@override
60+
Future<GoogleAuthCredential> authenticate() async {
61+
try {
62+
await initialize();
63+
final account = await _googleSignIn.authenticate(
64+
scopeHint: _googleScopes,
65+
);
66+
return _credentialFromAccount(account);
67+
} on GoogleSignInException catch (error) {
68+
if (error.code == GoogleSignInExceptionCode.canceled) {
69+
throw const GoogleAuthenticationCanceledException();
70+
}
71+
rethrow;
72+
}
73+
}
74+
75+
@override
76+
Future<void> disconnect() async {
77+
try {
78+
await _googleSignIn.disconnect();
79+
AppLogger.debug('Google Sign-In disconnected');
80+
} catch (error) {
81+
AppLogger.debug(
82+
'Google Sign-In disconnect failed errorType=${error.runtimeType}',
83+
);
84+
}
85+
}
86+
87+
GoogleAuthCredential _credentialFromAccount(GoogleSignInAccount account) {
88+
final idToken = account.authentication.idToken;
89+
if (idToken == null) {
90+
throw Exception('Google ID Token is null');
91+
}
92+
return GoogleAuthCredential(idToken: idToken);
93+
}
94+
95+
String? get _googleClientId {
96+
if (kIsWeb) return null;
97+
return defaultTargetPlatform == TargetPlatform.iOS
98+
? _googleIosClientId
99+
: null;
100+
}
101+
}

lib/data/repositories/user_repository_impl.dart

Lines changed: 9 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,34 @@
11
import 'dart:async';
22

33
import 'package:dio/dio.dart';
4-
import 'package:flutter/foundation.dart';
5-
import 'package:google_sign_in/google_sign_in.dart';
64
import 'package:injectable/injectable.dart';
75
import 'package:on_time_front/core/logging/app_logger.dart';
6+
import 'package:on_time_front/core/services/google_authentication_service.dart';
87
import 'package:on_time_front/core/validation/backend_constraints.dart';
98
import 'package:on_time_front/data/data_sources/authentication_remote_data_source.dart';
109
import 'package:on_time_front/data/data_sources/token_local_data_source.dart';
1110
import 'package:on_time_front/data/models/sign_in_with_google_request_model.dart';
1211
import 'package:on_time_front/data/models/sign_in_with_apple_request_model.dart';
12+
import 'package:on_time_front/domain/entities/google_auth_credential.dart';
1313
import 'package:on_time_front/domain/entities/user_entity.dart';
1414
import 'package:on_time_front/domain/repositories/user_repository.dart';
1515
import 'package:rxdart/subjects.dart';
1616

1717
@Singleton(as: UserRepository)
1818
class UserRepositoryImpl implements UserRepository {
19-
static const _googleIosClientId =
20-
'456571312261-r35ah9qi0qaq7al007e2db0e0jmjcmb4.apps.googleusercontent.com';
21-
static const _googleServerClientId =
22-
'456571312261-5kuf2r6i5i7lqjr7qealv06sdgkn3hcp.apps.googleusercontent.com';
23-
static const _googleScopes = ['email', 'profile'];
24-
2519
final AuthenticationRemoteDataSource _authenticationRemoteDataSource;
2620
final TokenLocalDataSource _tokenLocalDataSource;
27-
final GoogleSignIn _googleSignIn = GoogleSignIn.instance;
28-
Future<void>? _googleSignInInitialization;
21+
final GoogleAuthenticationService _googleAuthenticationService;
2922
late final _userStreamController = BehaviorSubject<UserEntity>.seeded(
3023
const UserEntity.empty(),
3124
);
3225

33-
@override
34-
Stream<GoogleSignInAuthenticationEvent> get googleAuthenticationEvents =>
35-
_googleSignIn.authenticationEvents;
36-
37-
@override
38-
bool get supportsGoogleAuthenticate => _googleSignIn.supportsAuthenticate();
39-
4026
UserRepositoryImpl(
4127
this._authenticationRemoteDataSource,
4228
this._tokenLocalDataSource,
29+
this._googleAuthenticationService,
4330
);
4431

45-
@override
46-
Future<void> initializeGoogleSignIn() {
47-
return _googleSignInInitialization ??= _initializeGoogleSignIn();
48-
}
49-
50-
Future<void> _initializeGoogleSignIn() async {
51-
await _googleSignIn.initialize(
52-
clientId: _googleClientId,
53-
serverClientId: _googleServerClientId,
54-
);
55-
}
56-
57-
@override
58-
Future<GoogleSignInAccount> authenticateWithGoogle() async {
59-
await initializeGoogleSignIn();
60-
return _googleSignIn.authenticate(scopeHint: _googleScopes);
61-
}
62-
6332
@override
6433
Future<UserEntity> getUser() async {
6534
try {
@@ -122,16 +91,14 @@ class UserRepositoryImpl implements UserRepository {
12291
}
12392

12493
@override
125-
Future<void> signInWithGoogle(GoogleSignInAccount googleUser) async {
94+
Future<void> signInWithGoogle(GoogleAuthCredential credential) async {
12695
try {
127-
final GoogleSignInAuthentication googleAuth = googleUser.authentication;
128-
final String? idToken = googleAuth.idToken;
129-
if (idToken == null) {
96+
if (credential.idToken.isEmpty) {
13097
throw Exception('Google ID Token is null');
13198
}
13299
final signInWithGoogleRequestModel = SignInWithGoogleRequestModel(
133-
idToken: idToken,
134-
refreshToken: '',
100+
idToken: credential.idToken,
101+
refreshToken: credential.refreshToken,
135102
);
136103
await _tokenLocalDataSource.deleteToken();
137104
final result = await _authenticationRemoteDataSource.signInWithGoogle(
@@ -225,8 +192,7 @@ class UserRepositoryImpl implements UserRepository {
225192
@override
226193
Future<void> disconnectGoogleSignIn() async {
227194
try {
228-
await _googleSignIn.disconnect();
229-
AppLogger.debug('Google Sign-In disconnected');
195+
await _googleAuthenticationService.disconnect();
230196
} catch (error) {
231197
AppLogger.debug(
232198
'Google Sign-In disconnect failed errorType=${error.runtimeType}',
@@ -237,11 +203,4 @@ class UserRepositoryImpl implements UserRepository {
237203
@override
238204
Stream<UserEntity> get userStream =>
239205
_userStreamController.asBroadcastStream();
240-
241-
String? get _googleClientId {
242-
if (kIsWeb) return null;
243-
return defaultTargetPlatform == TargetPlatform.iOS
244-
? _googleIosClientId
245-
: null;
246-
}
247206
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class GoogleAuthCredential {
2+
const GoogleAuthCredential({required this.idToken, this.refreshToken = ''});
3+
4+
final String idToken;
5+
final String refreshToken;
6+
}

lib/domain/repositories/user_repository.dart

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
1-
import 'package:google_sign_in/google_sign_in.dart';
1+
import 'package:on_time_front/domain/entities/google_auth_credential.dart';
22
import 'package:on_time_front/domain/entities/user_entity.dart';
33

44
abstract interface class UserRepository {
55
Stream<UserEntity> get userStream;
66

7-
Stream<GoogleSignInAuthenticationEvent> get googleAuthenticationEvents;
8-
9-
Future<void> initializeGoogleSignIn();
10-
11-
bool get supportsGoogleAuthenticate;
12-
13-
Future<GoogleSignInAccount> authenticateWithGoogle();
14-
157
Future<void> signUp({
168
required String email,
179
required String password,
@@ -22,7 +14,7 @@ abstract interface class UserRepository {
2214

2315
Future<void> signOut();
2416

25-
Future<void> signInWithGoogle(GoogleSignInAccount account);
17+
Future<void> signInWithGoogle(GoogleAuthCredential credential);
2618

2719
Future<void> signInWithApple({
2820
required String idToken,

lib/presentation/login/components/google_sign_in_button/google_sign_in_button_web.dart

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import 'dart:async';
22

33
import 'package:flutter/widgets.dart';
4-
import 'package:google_sign_in/google_sign_in.dart';
54
import 'package:google_sign_in_web/web_only.dart';
65
import 'package:on_time_front/core/di/di_setup.dart';
6+
import 'package:on_time_front/core/services/google_authentication_service.dart';
7+
import 'package:on_time_front/domain/entities/google_auth_credential.dart';
78
import 'package:on_time_front/domain/repositories/user_repository.dart';
89

910
class GoogleSignInButton extends StatefulWidget {
@@ -16,26 +17,27 @@ class GoogleSignInButton extends StatefulWidget {
1617
}
1718

1819
class _GoogleSignInButtonState extends State<GoogleSignInButton> {
19-
final authenticationRepository = getIt.get<UserRepository>();
20-
late final Stream<GoogleSignInAuthenticationEvent> _authenticationEvents;
21-
StreamSubscription<GoogleSignInAuthenticationEvent>?
22-
_authenticationEventsSubscription;
20+
final googleAuthenticationService = getIt.get<GoogleAuthenticationService>();
21+
late final Stream<GoogleAuthCredential> _authenticationCredentials;
22+
StreamSubscription<GoogleAuthCredential>?
23+
_authenticationCredentialsSubscription;
2324

2425
@override
2526
void initState() {
26-
_authenticationEvents = authenticationRepository.googleAuthenticationEvents;
27-
unawaited(authenticationRepository.initializeGoogleSignIn());
28-
_authenticationEventsSubscription = _authenticationEvents.listen((event) {
29-
if (event is GoogleSignInAuthenticationEventSignIn) {
30-
unawaited(getIt.get<UserRepository>().signInWithGoogle(event.user));
31-
}
27+
_authenticationCredentials =
28+
googleAuthenticationService.authenticationCredentials;
29+
unawaited(googleAuthenticationService.initialize());
30+
_authenticationCredentialsSubscription = _authenticationCredentials.listen((
31+
credential,
32+
) {
33+
unawaited(getIt.get<UserRepository>().signInWithGoogle(credential));
3234
});
3335
super.initState();
3436
}
3537

3638
@override
3739
void dispose() {
38-
unawaited(_authenticationEventsSubscription?.cancel());
40+
unawaited(_authenticationCredentialsSubscription?.cancel());
3941
super.dispose();
4042
}
4143

lib/presentation/login/screens/sign_in_main_screen.dart

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
22
import 'package:flutter_svg/flutter_svg.dart';
33
import 'package:flutter/foundation.dart';
44
import 'dart:io' show Platform;
5-
import 'package:google_sign_in/google_sign_in.dart';
65
import 'package:on_time_front/core/di/di_setup.dart';
76
import 'package:on_time_front/core/logging/app_logger.dart';
7+
import 'package:on_time_front/core/services/google_authentication_service.dart';
88
import 'package:on_time_front/domain/repositories/user_repository.dart';
99
import 'package:on_time_front/l10n/app_localizations.dart';
1010
import 'package:on_time_front/presentation/shared/components/modal_wide_button.dart';
@@ -105,8 +105,10 @@ class _SignInMainScreenState extends State<SignInMainScreen> {
105105

106106
Future<void> _defaultGoogleSignIn() async {
107107
final userRepository = getIt.get<UserRepository>();
108-
final googleAccount = await userRepository.authenticateWithGoogle();
109-
await userRepository.signInWithGoogle(googleAccount);
108+
final credential = await getIt
109+
.get<GoogleAuthenticationService>()
110+
.authenticate();
111+
await userRepository.signInWithGoogle(credential);
110112
}
111113

112114
Future<void> _defaultAppleSignIn() async {
@@ -137,8 +139,7 @@ class _SignInMainScreenState extends State<SignInMainScreen> {
137139
}
138140

139141
bool _isUserCancellation(Object error) {
140-
return error is GoogleSignInException &&
141-
error.code == GoogleSignInExceptionCode.canceled ||
142+
return error is GoogleAuthenticationCanceledException ||
142143
error is SignInWithAppleAuthorizationException &&
143144
error.code == AuthorizationErrorCode.canceled;
144145
}

0 commit comments

Comments
 (0)