Skip to content

Commit 5aa9335

Browse files
authored
Merge pull request #66 from nimblehq/feature/13-integrate-keep-user-logged-in
2 parents 497554d + 492d7fe commit 5aa9335

File tree

14 files changed

+279
-86
lines changed

14 files changed

+279
-86
lines changed

lib/api/data_sources/token_data_source.dart

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:survey_flutter/model/api_token.dart';
66
import 'package:survey_flutter/model/request/refresh_token_request.dart';
77
import 'package:survey_flutter/storage/secure_storage.dart';
88
import 'package:survey_flutter/storage/secure_storage_impl.dart';
9+
import 'package:survey_flutter/utils/serializer/api_token_serializer.dart';
910

1011
final tokenDataSourceProvider = Provider<TokenDataSource>((ref) {
1112
return TokenDataSourceImpl(ref.watch(secureStorageProvider),
@@ -29,24 +30,29 @@ class TokenDataSourceImpl extends TokenDataSource {
2930

3031
@override
3132
Future<ApiToken> getToken({bool forceRefresh = false}) async {
32-
if (forceRefresh) {
33-
final tokenResponse = await _authenticationApiService.refreshToken(
34-
RefreshTokenRequest(
35-
grantType: _grantType,
36-
clientId: Env.clientId,
37-
clientSecret: Env.clientSecret,
38-
),
39-
);
40-
final apiToken = tokenResponse.toApiToken();
41-
await _secureStorage.save(
42-
value: apiToken,
43-
key: SecureStorageKey.apiToken,
44-
);
45-
return apiToken;
33+
final currentToken = await _secureStorage.getValue(
34+
key: SecureStorageKey.apiToken,
35+
serializer: ApiTokenSerializer(),
36+
);
37+
38+
if (!forceRefresh) {
39+
return currentToken;
4640
}
4741

48-
return await _secureStorage.getValue<ApiToken>(
49-
key: SecureStorageKey.apiToken);
42+
final tokenResponse = await _authenticationApiService.refreshToken(
43+
RefreshTokenRequest(
44+
grantType: _grantType,
45+
refreshToken: currentToken.refreshToken,
46+
clientId: Env.clientId,
47+
clientSecret: Env.clientSecret,
48+
),
49+
);
50+
final newToken = tokenResponse.toApiToken();
51+
await _secureStorage.save(
52+
value: newToken,
53+
key: SecureStorageKey.apiToken,
54+
);
55+
return newToken;
5056
}
5157

5258
@override

lib/model/api_token.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import 'package:json_annotation/json_annotation.dart';
2-
import 'package:survey_flutter/storage/secure_storage.dart';
2+
import 'package:survey_flutter/utils/serializer/serializable.dart';
33

44
part 'api_token.g.dart';
55

66
@JsonSerializable()
7-
class ApiToken extends SecureStorageModel {
7+
class ApiToken extends Serializable {
88
@JsonKey(name: 'access_token')
99
final String accessToken;
1010
@JsonKey(name: 'refresh_token')

lib/model/request/refresh_token_request.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ part 'refresh_token_request.g.dart';
66
class RefreshTokenRequest {
77
@JsonKey(name: 'grant_type')
88
final String grantType;
9+
@JsonKey(name: 'refresh_token')
10+
final String refreshToken;
911
@JsonKey(name: 'client_id')
1012
final String clientId;
1113
@JsonKey(name: 'client_secret')
1214
final String clientSecret;
1315

1416
RefreshTokenRequest({
1517
required this.grantType,
18+
required this.refreshToken,
1619
required this.clientId,
1720
required this.clientSecret,
1821
});

lib/screens/splash/splash_screen.dart

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import 'package:flutter/material.dart';
2+
import 'package:flutter_riverpod/flutter_riverpod.dart';
23
import 'package:go_router/go_router.dart';
34
import 'package:survey_flutter/gen/assets.gen.dart';
5+
import 'package:survey_flutter/screens/home/home_screen.dart';
46
import 'package:survey_flutter/screens/login/login_screen.dart';
7+
import 'package:survey_flutter/screens/splash/splash_view_model.dart';
58

6-
class SplashScreen extends StatefulWidget {
9+
class SplashScreen extends ConsumerStatefulWidget {
710
const SplashScreen({Key? key}) : super(key: key);
811

912
@override
10-
State<StatefulWidget> createState() => _SplashScreenState();
13+
ConsumerState<ConsumerStatefulWidget> createState() => _SplashScreenState();
1114
}
1215

13-
class _SplashScreenState extends State<SplashScreen> {
16+
class _SplashScreenState extends ConsumerState<SplashScreen> {
1417
double _logoOpacity = 0;
18+
bool? _isLoggedIn;
1519

1620
@override
1721
void initState() {
@@ -26,6 +30,9 @@ class _SplashScreenState extends State<SplashScreen> {
2630

2731
@override
2832
Widget build(BuildContext context) {
33+
ref.listen<AsyncValue<bool>>(splashViewModelProvider, (_, next) {
34+
next.whenData((result) => _isLoggedIn = result);
35+
});
2936
return Scaffold(
3037
body: LayoutBuilder(builder: (_, __) {
3138
return Stack(
@@ -49,7 +56,11 @@ class _SplashScreenState extends State<SplashScreen> {
4956
duration: const Duration(seconds: 1),
5057
child: Assets.images.splashLogoWhite.image(),
5158
onEnd: () {
52-
context.go(routePathLoginScreen);
59+
if (_isLoggedIn == true) {
60+
context.go(routePathHomeScreen);
61+
} else {
62+
context.go(routePathLoginScreen);
63+
}
5364
},
5465
);
5566
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import 'dart:async';
2+
3+
import 'package:flutter_riverpod/flutter_riverpod.dart';
4+
import 'package:survey_flutter/usecases/base/base_use_case.dart';
5+
import 'package:survey_flutter/usecases/check_user_logged_in_use_case.dart';
6+
7+
final splashViewModelProvider =
8+
AsyncNotifierProvider.autoDispose<SplashViewModel, bool>(
9+
SplashViewModel.new);
10+
11+
class SplashViewModel extends AutoDisposeAsyncNotifier<bool> {
12+
@override
13+
FutureOr<bool> build() async {
14+
return _checkUserLoggedIn();
15+
}
16+
17+
Future<bool> _checkUserLoggedIn() async {
18+
final checkUserLoggedInUseCase = ref.read(checkUserLoggedInUseCaseProvider);
19+
final result = await checkUserLoggedInUseCase();
20+
return result is Success;
21+
}
22+
}

lib/storage/secure_storage.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'package:survey_flutter/utils/serializer/serializable.dart';
2+
13
enum SecureStorageKey {
24
apiToken,
35
}
@@ -11,15 +13,13 @@ extension SecureStorageKeyExt on SecureStorageKey {
1113
}
1214
}
1315

14-
abstract class SecureStorageModel {}
15-
1616
enum SecureStorageError {
1717
failToGetValue,
1818
}
1919

2020
abstract class SecureStorage {
21-
Future<void> save<M extends SecureStorageModel>(
21+
Future<void> save<M extends Serializable>(
2222
{required M value, required SecureStorageKey key});
23-
Future<M> getValue<M extends SecureStorageModel>(
24-
{required SecureStorageKey key});
23+
Future<M> getValue<M extends Serializable>(
24+
{required SecureStorageKey key, required Serializer<M> serializer});
2525
}

lib/storage/secure_storage_impl.dart

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import 'dart:convert';
22

33
import 'package:flutter_riverpod/flutter_riverpod.dart';
44
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
5-
import 'package:survey_flutter/model/api_token.dart';
65
import 'package:survey_flutter/storage/secure_storage.dart';
6+
import 'package:survey_flutter/utils/serializer/serializable.dart';
77

88
import '../di/provider/flutter_secure_storage.dart';
99

@@ -16,24 +16,23 @@ class SecureStorageImpl extends SecureStorage {
1616
SecureStorageImpl(this._storage);
1717

1818
@override
19-
Future<M> getValue<M extends SecureStorageModel>(
20-
{required SecureStorageKey key}) async {
19+
Future<M> getValue<M extends Serializable>({
20+
required SecureStorageKey key,
21+
required Serializer<M> serializer,
22+
}) async {
2123
final rawValue = await _storage.read(key: key.string);
2224
if (rawValue == null) {
2325
throw SecureStorageError.failToGetValue;
2426
}
2527
final jsonValue = await jsonDecode(rawValue);
26-
27-
if (M == ApiToken) {
28-
return ApiToken.fromJson(jsonValue) as M;
29-
} else {
30-
throw ArgumentError('Invalid SecureStorageModel type');
31-
}
28+
return serializer.serialize(jsonValue);
3229
}
3330

3431
@override
35-
Future<void> save<M extends SecureStorageModel>(
36-
{required M value, required SecureStorageKey key}) async {
32+
Future<void> save<M extends Serializable>({
33+
required M value,
34+
required SecureStorageKey key,
35+
}) async {
3736
final encodedValue = jsonEncode(value);
3837
await _storage.write(key: key.string, value: encodedValue);
3938
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import 'package:flutter_riverpod/flutter_riverpod.dart';
2+
import 'package:survey_flutter/api/data_sources/token_data_source.dart';
3+
import 'package:survey_flutter/usecases/base/base_use_case.dart';
4+
5+
final checkUserLoggedInUseCaseProvider =
6+
Provider<CheckUserLoggedInUseCase>((ref) {
7+
return CheckUserLoggedInUseCase(ref.watch(tokenDataSourceProvider));
8+
});
9+
10+
class CheckUserLoggedInUseCase implements NoParamsUseCase<bool> {
11+
final TokenDataSource _tokenDataSource;
12+
13+
CheckUserLoggedInUseCase(this._tokenDataSource);
14+
@override
15+
Future<Result<bool>> call() async {
16+
try {
17+
final _ = await _tokenDataSource.getToken();
18+
return Success(true);
19+
} catch (error) {
20+
return Failed(UseCaseException(error));
21+
}
22+
}
23+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import 'package:survey_flutter/model/api_token.dart';
2+
import 'package:survey_flutter/utils/serializer/serializable.dart';
3+
4+
class ApiTokenSerializer extends Serializer<ApiToken> {
5+
@override
6+
ApiToken serialize(Map<String, dynamic> json) {
7+
return ApiToken.fromJson(json);
8+
}
9+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
abstract class Serializable {}
2+
3+
abstract class Serializer<T extends Serializable> {
4+
T serialize(Map<String, dynamic> json);
5+
}

0 commit comments

Comments
 (0)