Skip to content

Commit 62cdcbc

Browse files
authored
Merge pull request #57 from nimblehq/feature/10-integrate-sign-in
[#10] [Integrate] As a user, I can sign in with email and password
2 parents be5b16a + 8343044 commit 62cdcbc

File tree

13 files changed

+193
-73
lines changed

13 files changed

+193
-73
lines changed

lib/di/provider/dio_provider.dart

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import 'package:dio/dio.dart';
22
import 'package:flutter/foundation.dart';
33
import 'package:survey_flutter/di/interceptor/app_interceptor.dart';
4+
import 'package:survey_flutter/env.dart';
45

5-
const String headerContentType = 'Content-Type';
6-
const String defaultContentType = 'application/json; charset=utf-8';
6+
const String _headerContentType = 'Content-Type';
7+
const String _defaultContentType = 'application/json; charset=utf-8';
78

89
class DioProvider {
910
Dio? _dio;
@@ -31,9 +32,10 @@ class DioProvider {
3132
}
3233

3334
return dio
34-
..options.connectTimeout = const Duration(seconds: 3000)
35-
..options.receiveTimeout = const Duration(seconds: 5000)
36-
..options.headers = {headerContentType: defaultContentType}
37-
..interceptors.addAll(interceptors);
35+
..options.connectTimeout = const Duration(seconds: 3)
36+
..options.receiveTimeout = const Duration(seconds: 5)
37+
..options.headers = {_headerContentType: _defaultContentType}
38+
..interceptors.addAll(interceptors)
39+
..options.baseUrl = Env.restApiEndpoint;
3840
}
3941
}

lib/main.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:flutter_config/flutter_config.dart';
33
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
44
import 'package:flutter_riverpod/flutter_riverpod.dart';
55
import 'package:go_router/go_router.dart';
6+
import 'package:survey_flutter/screens/home/home_screen.dart';
67
import 'package:survey_flutter/screens/login/login_screen.dart';
78
import 'package:survey_flutter/screens/splash/splash_screen.dart';
89
import 'package:survey_flutter/theme/app_theme.dart';
@@ -34,6 +35,10 @@ class App extends StatelessWidget {
3435
child: LoginScreen(),
3536
),
3637
),
38+
GoRoute(
39+
path: routePathHomeScreen,
40+
builder: (_, __) => const HomeScreen(),
41+
),
3742
],
3843
);
3944

lib/repositories/authentication_repository.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
1+
import 'package:flutter_riverpod/flutter_riverpod.dart';
12
import 'package:survey_flutter/api/authentication_api_service.dart';
23
import 'package:survey_flutter/api/exception/network_exceptions.dart';
4+
import 'package:survey_flutter/di/provider/dio_provider.dart';
35
import 'package:survey_flutter/env.dart';
46
import 'package:survey_flutter/model/login_model.dart';
57
import 'package:survey_flutter/model/request/login_request.dart';
6-
import 'package:injectable/injectable.dart';
78

89
const String _grantType = "password";
910

11+
final authenticationRepositoryProvider =
12+
Provider<AuthenticationRepository>((_) {
13+
return AuthenticationRepositoryImpl(
14+
AuthenticationApiService(DioProvider().getDio()),
15+
);
16+
});
17+
1018
abstract class AuthenticationRepository {
1119
Future<LoginModel> login({
1220
required String email,
1321
required String password,
1422
});
1523
}
1624

17-
@Singleton(as: AuthenticationRepository)
1825
class AuthenticationRepositoryImpl extends AuthenticationRepository {
1926
final AuthenticationApiService _authenticationApiService;
2027

lib/screens/home/home_screen.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import 'package:survey_flutter/screens/home/home_header_widget.dart';
33
import 'package:survey_flutter/screens/home/home_pages_widget.dart';
44
import 'package:survey_flutter/screens/home/home_page_indicator_widget.dart';
55

6+
const routePathHomeScreen = '/home';
7+
68
class HomeScreen extends StatelessWidget {
79
const HomeScreen({Key? key}) : super(key: key);
810

lib/screens/login/login_form.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:survey_flutter/theme/primary_text_field_decoration.dart';
77
import 'package:survey_flutter/utils/build_context_ext.dart';
88

99
const _fieldSpacing = 20.0;
10+
const _loadingIndicatorSize = 28.0;
1011

1112
class LoginForm extends ConsumerStatefulWidget {
1213
const LoginForm({Key? key}) : super(key: key);
@@ -55,7 +56,20 @@ class _LoginFormState extends ConsumerState<LoginForm> {
5556
ElevatedButton get _loginButton => ElevatedButton(
5657
style: PrimaryButtonStyle(hintTextStyle: context.textTheme.labelMedium),
5758
onPressed: _submit,
58-
child: Text(context.localizations.loginButton),
59+
child: Consumer(
60+
builder: (_, widgetRef, __) {
61+
final loginVievModel = widgetRef.watch(loginViewModelProvider);
62+
return (loginVievModel.isLoading)
63+
? const SizedBox(
64+
width: _loadingIndicatorSize,
65+
height: _loadingIndicatorSize,
66+
child: CircularProgressIndicator(
67+
color: Colors.black45,
68+
),
69+
)
70+
: Text(context.localizations.loginButton);
71+
},
72+
),
5973
);
6074

6175
String? _validateEmail(String? email) {

lib/screens/login/login_screen.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import 'dart:ui';
22

33
import 'package:flutter/material.dart';
44
import 'package:flutter_riverpod/flutter_riverpod.dart';
5+
import 'package:go_router/go_router.dart';
56
import 'package:survey_flutter/gen/assets.gen.dart';
7+
import 'package:survey_flutter/screens/home/home_screen.dart';
68
import 'package:survey_flutter/screens/login/login_form.dart';
79
import 'package:survey_flutter/screens/login/login_view_model.dart';
810
import 'package:survey_flutter/theme/app_constants.dart';
@@ -115,9 +117,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
115117
_setUpListener(BuildContext context) {
116118
ref.listen<AsyncValue<void>>(loginViewModelProvider, (_, next) {
117119
next.maybeWhen(
118-
data: (_) {
119-
// TODO: Navigate to the Home screen
120-
},
120+
data: (_) => context.go(routePathHomeScreen),
121121
error: (error, _) {
122122
showAlertDialog(
123123
context: context,

lib/screens/login/login_view_model.dart

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import 'dart:async';
22

33
import 'package:flutter_riverpod/flutter_riverpod.dart';
4+
import 'package:survey_flutter/api/exception/network_exceptions.dart';
45
import 'package:survey_flutter/uimodels/app_error.dart';
6+
import 'package:survey_flutter/usecases/base/base_use_case.dart';
7+
import 'package:survey_flutter/usecases/login_use_case.dart';
58
import 'package:survey_flutter/utils/internet_connection_manager.dart';
69

710
final loginViewModelProvider =
811
AsyncNotifierProvider.autoDispose<LoginViewModel, void>(LoginViewModel.new);
912

1013
class LoginViewModel extends AutoDisposeAsyncNotifier<void> {
11-
late InternetConnectionManager internetConnectionManager;
12-
1314
bool isValidEmail(String? email) {
1415
// Just use a simple rule, no fancy Regex!
1516
return !(email == null || !email.contains('@'));
@@ -24,31 +25,49 @@ class LoginViewModel extends AutoDisposeAsyncNotifier<void> {
2425
required String password,
2526
}) async {
2627
state = const AsyncLoading();
27-
// TODO: Integrate with API
28-
29-
// Handling error part:
28+
final loginUseCase = ref.read(loginUseCaseProvider);
29+
final result = await loginUseCase(
30+
LoginParams(
31+
email: email,
32+
password: password,
33+
),
34+
);
3035

31-
// If it returns unauthorized error (401)
32-
//state = const AsyncError(
33-
// AppError.unauthorized,
34-
// StackTrace.empty,
35-
//);
36+
if (result is Failed) {
37+
final error = result as Failed;
38+
final exception = error.exception.actualException as NetworkExceptions;
3639

37-
// If it returns timeout error, then check Internet connection
38-
internetConnectionManager = ref.read(internetConnectionManagerProvider);
39-
final isConnected = await internetConnectionManager.hasConnection();
40+
if (exception is BadRequest || exception is UnauthorisedRequest) {
41+
state = const AsyncError(
42+
AppError.unauthorized,
43+
StackTrace.empty,
44+
);
45+
return;
46+
} else if (exception is RequestTimeout) {
47+
final isConnected = await _hasInternetConnection();
48+
if (!isConnected) {
49+
state = const AsyncError(
50+
AppError.noInternetConnection,
51+
StackTrace.empty,
52+
);
53+
return;
54+
}
55+
}
4056

41-
if (!isConnected) {
42-
state = const AsyncError(
43-
AppError.noInternetConnection,
44-
StackTrace.empty,
45-
);
46-
} else {
4757
state = const AsyncError(
4858
AppError.generic,
4959
StackTrace.empty,
5060
);
61+
return;
5162
}
63+
64+
state = const AsyncData(null);
65+
}
66+
67+
Future<bool> _hasInternetConnection() async {
68+
final internetConnectionManager =
69+
ref.read(internetConnectionManagerProvider);
70+
return await internetConnectionManager.hasConnection();
5271
}
5372

5473
@override

lib/usecases/login_use_case.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
import 'package:flutter_riverpod/flutter_riverpod.dart';
12
import 'dart:async';
23
import 'package:survey_flutter/model/login_model.dart';
34
import 'package:survey_flutter/repositories/authentication_repository.dart';
45
import 'package:survey_flutter/usecases/base/base_use_case.dart';
5-
import 'package:injectable/injectable.dart';
6+
7+
final loginUseCaseProvider = Provider<LoginUseCase>((ref) {
8+
return LoginUseCase(ref.watch(authenticationRepositoryProvider));
9+
});
610

711
class LoginParams {
812
final String email;
@@ -14,7 +18,6 @@ class LoginParams {
1418
});
1519
}
1620

17-
@Injectable()
1821
class LoginUseCase extends UseCase<LoginModel, LoginParams> {
1922
final AuthenticationRepository _repository;
2023

pubspec.lock

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -335,14 +335,6 @@ packages:
335335
description: flutter
336336
source: sdk
337337
version: "0.0.0"
338-
get_it:
339-
dependency: transitive
340-
description:
341-
name: get_it
342-
sha256: "529de303c739fca98cd7ece5fca500d8ff89649f1bb4b4e94fb20954abcd7468"
343-
url: "https://pub.dev"
344-
source: hosted
345-
version: "7.6.0"
346338
glob:
347339
dependency: transitive
348340
description:
@@ -391,14 +383,6 @@ packages:
391383
url: "https://pub.dev"
392384
source: hosted
393385
version: "4.0.2"
394-
injectable:
395-
dependency: "direct main"
396-
description:
397-
name: injectable
398-
sha256: f71eb879124ed286cbd2210337b91ff5f345f146187c1f1891c172e0ac06443a
399-
url: "https://pub.dev"
400-
source: hosted
401-
version: "1.5.4"
402386
integration_test:
403387
dependency: "direct dev"
404388
description: flutter

pubspec.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ dependencies:
4545
retrofit: ^4.0.1
4646
japx: ^2.0.5
4747
equatable: ^2.0.0
48-
injectable: ^1.5.0
4948
internet_connection_checker: ^1.0.0+1
5049
page_view_dot_indicator: ^2.1.0
5150

0 commit comments

Comments
 (0)