Skip to content

Commit 898e86a

Browse files
committed
feat: bring the project up to speed
1 parent ff0a304 commit 898e86a

File tree

43 files changed

+808
-586
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+808
-586
lines changed

lib/bootstrap.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import 'package:example/config/providers.dart' as providers;
22
import 'package:example/flavors.dart';
33
import 'package:flutter/material.dart';
44
import 'package:flutter_riverpod/flutter_riverpod.dart';
5+
import 'package:flutter_web_plugins/url_strategy.dart';
56

67
/// Initializes services and controllers before the start of the application
78
Future<ProviderContainer> bootstrap() async {
89
WidgetsFlutterBinding.ensureInitialized();
10+
usePathUrlStrategy();
911

1012
final container = ProviderContainer(
1113
overrides: [], //supabaseProvider.overrideWithValue(Supabase.instance)

lib/config/providers.dart

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import 'dart:convert';
22

3-
import 'package:example/features/auth/auth_provider.dart';
3+
import 'package:example/features/auth/application/auth_controller.dart';
44
import 'package:example/features/common/infrastructure/entities/environment.dart';
55
import 'package:example/flavors.dart';
66
import 'package:flutter/foundation.dart';
77
import 'package:flutter/services.dart';
88
import 'package:flutter_riverpod/flutter_riverpod.dart';
9-
import 'package:go_router/go_router.dart';
109
import 'package:shared_preferences/shared_preferences.dart';
1110
import 'package:supabase_flutter/supabase_flutter.dart' as supabase;
1211

@@ -47,8 +46,6 @@ final supabaseClientProvider = Provider<supabase.SupabaseClient>(
4746
4847
/// Triggered from bootstrap() to complete futures
4948
Future<void> initializeProviders(ProviderContainer container) async {
50-
GoRouter.setUrlPathStrategy(UrlPathStrategy.path);
51-
5249
/// Core
5350
await container.read(sharedPreferencesProvider.future);
5451
await container.read(supabaseProvider.future);

lib/config/router.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ final router = GoRouter(
8787
observers: [
8888
routeObserver,
8989
],
90-
redirect: (state) {
90+
redirect: (context, state) {
9191
final loggedIn = authStateListenable.value;
9292
final goingToLogin = state.subloc.contains('/${AuthScreen.route}');
9393

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,34 @@
11
import 'package:example/features/auth/auth_provider.dart';
22
import 'package:example/features/auth/domain/entities/user_entity.dart';
3-
import 'package:example/features/auth/infrastructure/repositories/auth_repository.dart';
4-
import 'package:flutter_riverpod/flutter_riverpod.dart';
3+
import 'package:riverpod_annotation/riverpod_annotation.dart';
54
import 'package:uni_links/uni_links.dart';
65

7-
/// State controller for authentication
8-
class AuthController extends StateNotifier<UserEntity?> {
9-
///
10-
AuthController(this._read) : super(null) {
11-
_initialize();
12-
}
13-
final Reader _read;
14-
AuthRepository get _repository => _read(authRepositoryProvider);
6+
part 'auth_controller.g.dart';
157

16-
///
17-
Future<void> _initialize() async {
18-
/// try to restore saved session
19-
final res = await _repository.restoreSession();
20-
state = res.fold((l) => null, (r) => r);
8+
/// State controller for authentication
9+
@riverpod
10+
class AuthController extends _$AuthController {
11+
@override
12+
Future<UserEntity?> build() async {
13+
final repository = ref.watch(authRepositoryProvider);
14+
final res = await repository.restoreSession();
15+
state = res.fold((l) => AsyncError(l.error, l.stackTrace), AsyncData.new);
2116
_updateAuthState();
22-
if (state == null) {
17+
if (state.valueOrNull == null) {
2318
/// try to create session from deep link
2419
await _handleInitialDeepLink();
2520
}
2621

2722
/// listen to auth changes
28-
_repository.authStateChange((user) {
29-
state = user;
23+
repository.authStateChange((user) {
24+
state = AsyncData(user);
3025
_updateAuthState();
3126
});
32-
// TODO(vh): how to cancel subscription override dispose
27+
return null;
3328
}
3429

3530
void _updateAuthState() {
36-
authStateListenable.value = state != null;
31+
authStateListenable.value = state.valueOrNull != null;
3732
}
3833

3934
///
@@ -51,13 +46,13 @@ class AuthController extends StateNotifier<UserEntity?> {
5146
?.substring(refreshTokenQueryParam.indexOf('=') + 1);
5247
if (refreshToken == null) return;
5348

54-
final res = await _repository.setSession(refreshToken);
55-
state = res.fold((l) => null, (r) => r);
49+
final res = await ref.read(authRepositoryProvider).setSession(refreshToken);
50+
state = res.fold((l) => AsyncError(l.error, l.stackTrace), AsyncData.new);
5651
_updateAuthState();
5752
}
5853

5954
/// Signs out user
6055
Future<void> signOut() async {
61-
await _repository.signOut();
56+
await ref.read(authRepositoryProvider).signOut();
6257
}
6358
}
Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
1-
import 'dart:developer';
2-
31
import 'package:example/features/auth/auth_provider.dart';
4-
import 'package:flutter_riverpod/flutter_riverpod.dart';
2+
import 'package:riverpod_annotation/riverpod_annotation.dart';
53

6-
///s
7-
class SignInWithGoogleController extends StateNotifier<bool> {
8-
///
9-
SignInWithGoogleController(this._read) : super(false);
10-
final Reader _read;
4+
part 'sign_in_with_google_controller.g.dart';
115

12-
/// Signs in using Supabase auth
13-
Future<void> signInWithGoogle() async {
14-
log('here');
15-
await _read(authRepositoryProvider).signInWithGoogle();
16-
}
6+
///
7+
@riverpod
8+
FutureOr<void> signInWithGoogle(SignInWithGoogleRef ref) async {
9+
await ref.watch(authRepositoryProvider).signInWithGoogle();
1710
}

lib/features/auth/auth_provider.dart

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
11
import 'package:example/config/providers.dart';
2-
import 'package:example/features/auth/application/auth_controller.dart';
3-
import 'package:example/features/auth/application/sign_in_with_google_controller.dart';
4-
import 'package:example/features/auth/domain/entities/user_entity.dart';
52
import 'package:example/features/auth/infrastructure/datasources/local/auth_token_local_data_source.dart';
63
import 'package:example/features/auth/infrastructure/repositories/auth_repository.dart';
74
import 'package:flutter/foundation.dart';
8-
import 'package:flutter_riverpod/flutter_riverpod.dart';
5+
import 'package:riverpod_annotation/riverpod_annotation.dart';
6+
7+
part 'auth_provider.g.dart';
98

109
///
1110
/// Infrastructure dependencies
1211
///
1312
14-
final authRepositoryProvider = Provider<AuthRepository>((ref) {
13+
@riverpod
14+
AuthRepository authRepository(AuthRepositoryRef ref) {
1515
final authClient = ref.watch(supabaseClientProvider).auth;
16-
final prefs = ref.read(sharedPreferencesProvider).asData!.value;
16+
final prefs = ref.read(sharedPreferencesProvider).valueOrNull!;
1717
return AuthRepository(
18-
AuthTokenLocalDataSource(
19-
prefs,
20-
),
18+
AuthTokenLocalDataSource(prefs),
2119
authClient,
2220
);
23-
});
21+
}
2422

2523
///
2624
/// Application dependencies
@@ -29,17 +27,6 @@ final authRepositoryProvider = Provider<AuthRepository>((ref) {
2927
/// Provides a [ValueNotifier] to the app router to redirect on auth state change
3028
final authStateListenable = ValueNotifier<bool>(false);
3129

32-
///
33-
final authControllerProvider =
34-
StateNotifierProvider<AuthController, UserEntity?>((ref) {
35-
return AuthController(ref.read);
36-
});
37-
38-
///
39-
final signInWithGoogleProvider =
40-
StateNotifierProvider<SignInWithGoogleController, bool>((ref) {
41-
return SignInWithGoogleController(ref.read);
42-
});
4330

4431
///
4532
/// Presentation dependencies

lib/features/auth/infrastructure/datasources/local/auth_token_local_data_source.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class AuthTokenLocalDataSource {
1515
Either<Failure, String> get() {
1616
final v = _prefs.getString(_key);
1717
if (v == null) {
18-
return left(const Failure.empty());
18+
return left(Failure.empty(StackTrace.current));
1919
}
2020

2121
return right(v);

lib/features/auth/infrastructure/repositories/auth_repository.dart

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ class AuthRepository implements AuthRepositoryInterface {
2828
void authStateChange(
2929
void Function(UserEntity? userEntity) callback,
3030
) {
31-
authClient.onAuthStateChange((event, session) {
32-
switch (event) {
31+
authClient.onAuthStateChange.listen((data) {
32+
switch (data.event) {
3333
case supabase.AuthChangeEvent.signedIn:
3434
callback(
35-
UserEntity.fromJson(session!.user!.toJson()),
35+
UserEntity.fromJson(data.session!.user.toJson()),
3636
);
3737

3838
break;
@@ -41,12 +41,13 @@ class AuthRepository implements AuthRepositoryInterface {
4141
break;
4242
case supabase.AuthChangeEvent.userUpdated:
4343
callback(
44-
UserEntity.fromJson(session!.user!.toJson()),
44+
UserEntity.fromJson(data.session!.user.toJson()),
4545
);
4646
break;
4747
case supabase.AuthChangeEvent.passwordRecovery:
4848
case supabase.AuthChangeEvent.tokenRefreshed:
49-
break;
49+
case supabase.AuthChangeEvent.userDeleted:
50+
case supabase.AuthChangeEvent.mfaChallengeVerified:
5051
}
5152
});
5253
}
@@ -56,49 +57,49 @@ class AuthRepository implements AuthRepositoryInterface {
5657
Future<Either<Failure, UserEntity>> setSession(String token) async {
5758
final response = await authClient.setSession(token);
5859
await authTokenLocalDataSource
59-
.store(response.data?.persistSessionString ?? '');
60+
.store(response.session?.persistSessionString ?? '');
6061

61-
final data = response.data;
62+
final data = response.session;
6263

63-
if (response.error != null || response.data == null) {
64+
if (data == null) {
6465
await authTokenLocalDataSource.remove();
65-
return left(const Failure.unauthorized());
66+
return left(Failure.unauthorized(StackTrace.current));
6667
}
6768

68-
return right(UserEntity.fromJson(data!.user!.toJson()));
69+
return right(UserEntity.fromJson(data.user.toJson()));
6970
}
7071

7172
/// Recovers session from local storage and refreshes tokens
7273
@override
7374
Future<Either<Failure, UserEntity>> restoreSession() async {
7475
final res = authTokenLocalDataSource.get();
7576
if (res.isLeft()) {
76-
return left(const Failure.empty());
77+
return left(Failure.empty(StackTrace.current));
7778
}
7879

7980
final response = await authClient.recoverSession(res.getOrElse((_) => ''));
80-
final data = response.data;
81+
final data = response.session;
8182

82-
if (response.error != null || response.data == null) {
83+
if (response.session == null) {
8384
await authTokenLocalDataSource.remove();
84-
return left(const Failure.unauthorized());
85+
return left(Failure.unauthorized(StackTrace.current));
8586
}
8687

8788
await authTokenLocalDataSource
88-
.store(response.data?.persistSessionString ?? '');
89+
.store(response.session?.persistSessionString ?? '');
8990

90-
return right(UserEntity.fromJson(data!.user!.toJson()));
91+
return right(UserEntity.fromJson(data!.user.toJson()));
9192
}
9293

9394
/// Signs in user to the application
9495
@override
9596
Future<Either<Failure, bool>> signInWithGoogle() async {
9697
log('here2');
97-
final res = await authClient.signInWithProvider(
98+
final res = await authClient.signInWithOAuth(
9899
supabase.Provider.google,
99100
);
100101
if (!res) {
101-
return left(const Failure.badRequest());
102+
return left(Failure.badRequest(StackTrace.current));
102103
}
103104
return right(true);
104105
}
@@ -107,11 +108,11 @@ class AuthRepository implements AuthRepositoryInterface {
107108
@override
108109
Future<Either<Failure, bool>> signOut() async {
109110
await authTokenLocalDataSource.remove();
110-
111-
final res = await authClient.signOut();
112-
if (res.error != null) {
113-
return left(const Failure.badRequest());
111+
try {
112+
await authClient.signOut();
113+
return right(true);
114+
} catch (e) {
115+
return left(Failure.badRequest(StackTrace.current));
114116
}
115-
return right(true);
116117
}
117118
}

lib/features/auth/presentation/widgets/sign_in_with_google_button.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import 'dart:developer';
22

3-
import 'package:example/features/auth/auth_provider.dart';
3+
import 'package:example/features/auth/application/sign_in_with_google_controller.dart';
44
import 'package:example/features/common/presentation/utils/extensions/ui_extension.dart';
55
import 'package:flutter/material.dart';
66
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -15,7 +15,7 @@ class SignInWithGoogleButton extends ConsumerWidget {
1515
return ElevatedButton(
1616
onPressed: () {
1717
try {
18-
ref.read(signInWithGoogleProvider.notifier).signInWithGoogle();
18+
ref.read(signInWithGoogleProvider.future);
1919
} catch (e) {
2020
log(e.toString());
2121
}

lib/features/common/domain/failures/failure.dart

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,32 @@ part 'failure.freezed.dart';
55
@freezed
66

77
/// Represents all app failures
8-
class Failure implements Exception {
9-
8+
class Failure with _$Failure implements Exception {
109
const Failure._();
1110

1211
/// Expected value is null or empty
13-
const factory Failure.empty() = _EmptyFailure;
12+
const factory Failure.empty(
13+
StackTrace stackTrace, [
14+
String? message,
15+
]) = _EmptyFailure;
1416

1517
/// Expected value has invalid format
16-
const factory Failure.unprocessableEntity({required String message}) =
17-
_UnprocessableEntityFailure;
18+
const factory Failure.unprocessableEntity(
19+
StackTrace stackTrace, {
20+
required String message,
21+
}) = _UnprocessableEntityFailure;
1822

1923
/// Represent 401 error
20-
const factory Failure.unauthorized() = _UnauthorizedFailure;
24+
const factory Failure.unauthorized(
25+
StackTrace stackTrace, [
26+
String? message,
27+
]) = _UnauthorizedFailure;
2128

2229
/// Represents 400 error
23-
const factory Failure.badRequest() = _BadRequestFailure;
30+
const factory Failure.badRequest(
31+
StackTrace stackTrace, [
32+
String? message,
33+
]) = _BadRequestFailure;
2434

2535
/// Get the error message for specified failure
2636
String get error => this is _UnprocessableEntityFailure

0 commit comments

Comments
 (0)