Skip to content

Commit

Permalink
[#8] Show no internet connection error dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
Thieurom committed Aug 15, 2023
1 parent 34cf7bf commit 6b4275d
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 11 deletions.
4 changes: 3 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"loginButton": "Log in",
"invalidEmailError": "Please enter the valid email format.",
"invalidPasswordError": "Password must be at least 8 characters long.",
"unauthenticatedError": "Your email or password is incorrect.\nPlease try again.",
"unauthorizedError": "Your email or password is incorrect.\nPlease try again.",
"noInternetConnectionError": "You seem to be offline.\nPlease try again!",
"genericError": "Something went wrong.\nPlease try again!",
"loginFailAlertTitle": "Unable to log in"
}
32 changes: 27 additions & 5 deletions lib/screens/login/login_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import 'dart:async';

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:survey_flutter/uimodels/app_error.dart';
import 'package:survey_flutter/utils/internet_connection_manager.dart';

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

class LoginViewModel extends AutoDisposeAsyncNotifier<void> {
late InternetConnectionManager internetConnectionManager;

bool isValidEmail(String? email) {
// Just use a simple rule, no fancy Regex!
return !(email == null || !email.contains('@'));
Expand All @@ -19,11 +22,30 @@ class LoginViewModel extends AutoDisposeAsyncNotifier<void> {
login({required String email, required String password}) async {
state = const AsyncLoading();
// TODO: Integrate with API
// For now, assume its returns error
state = const AsyncError(
AppError.unauthenticated,
StackTrace.empty,
);

// Handling error part:

// If it returns unauthorized error (401)
//state = const AsyncError(
// AppError.unauthorized,
// StackTrace.empty,
//);

// If it returns timeout error, then check Internet connection
internetConnectionManager = ref.read(internetConnectionManagerProvider);
final isConnected = await internetConnectionManager.hasConnection();

if (!isConnected) {
state = const AsyncError(
AppError.noInternetConnection,
StackTrace.empty,
);
} else {
state = const AsyncError(

Check warning on line 44 in lib/screens/login/login_view_model.dart

View check run for this annotation

Codecov / codecov/patch

lib/screens/login/login_view_model.dart#L44

Added line #L44 was not covered by tests
AppError.generic,
StackTrace.empty,
);
}
}

@override
Expand Down
14 changes: 11 additions & 3 deletions lib/uimodels/app_error.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import 'package:flutter/material.dart';
import 'package:survey_flutter/utils/build_context_ext.dart';

enum AppError { unauthenticated }
enum AppError {
unauthorized,
noInternetConnection,
generic,
}

extension AppErrorExtension on AppError {
String description(BuildContext context) {

Check warning on line 11 in lib/uimodels/app_error.dart

View check run for this annotation

Codecov / codecov/patch

lib/uimodels/app_error.dart#L11

Added line #L11 was not covered by tests
switch (this) {
case AppError.unauthenticated:
return context.localizations.unauthenticatedError;
case AppError.unauthorized:
return context.localizations.unauthorizedError;
case AppError.noInternetConnection:
return context.localizations.noInternetConnectionError;
case AppError.generic:
return context.localizations.genericError;

Check warning on line 18 in lib/uimodels/app_error.dart

View check run for this annotation

Codecov / codecov/patch

lib/uimodels/app_error.dart#L13-L18

Added lines #L13 - L18 were not covered by tests
}
}
}
21 changes: 21 additions & 0 deletions lib/utils/internet_connection_manager.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:internet_connection_checker/internet_connection_checker.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

abstract class InternetConnectionManager {
Future<bool> hasConnection();
}

final internetConnectionManagerProvider =
Provider<InternetConnectionManager>((_) {
return InternetConnectionManagerImpl();
});

class InternetConnectionManagerImpl extends InternetConnectionManager {
final InternetConnectionChecker _internetConnectionChecker =
InternetConnectionChecker();

@override

Check warning on line 17 in lib/utils/internet_connection_manager.dart

View check run for this annotation

Codecov / codecov/patch

lib/utils/internet_connection_manager.dart#L17

Added line #L17 was not covered by tests
Future<bool> hasConnection() async {
return await _internetConnectionChecker.hasConnection;

Check warning on line 19 in lib/utils/internet_connection_manager.dart

View check run for this annotation

Codecov / codecov/patch

lib/utils/internet_connection_manager.dart#L19

Added line #L19 was not covered by tests
}
}
8 changes: 8 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
internet_connection_checker:
dependency: "direct main"
description:
name: internet_connection_checker
sha256: "1c683e63e89c9ac66a40748b1b20889fd9804980da732bf2b58d6d5456c8e876"
url: "https://pub.dev"
source: hosted
version: "1.0.0+1"
intl:
dependency: "direct main"
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ dependencies:
japx: ^2.0.5
equatable: ^2.0.0
injectable: ^1.5.0
internet_connection_checker: ^1.0.0+1

dev_dependencies:
build_runner: ^2.4.4
Expand Down
2 changes: 2 additions & 0 deletions test/mocks/generate_mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import 'package:mockito/annotations.dart';
import 'package:survey_flutter/api/authentication_api_service.dart';
import 'package:survey_flutter/repositories/authentication_repository.dart';
import 'package:survey_flutter/utils/async_listener.dart';
import 'package:survey_flutter/utils/internet_connection_manager.dart';

@GenerateMocks([
AsyncListener,
AuthenticationApiService,
AuthenticationRepository,
DioError,
InternetConnectionManager,
])
main() {
// empty class to generate mock repository classes
Expand Down
32 changes: 30 additions & 2 deletions test/screens/login/login_view_model_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:survey_flutter/screens/login/login_view_model.dart';
import 'package:survey_flutter/utils/internet_connection_manager.dart';

import '../../mocks/generate_mocks.mocks.dart';

void main() {
group('LoginViewModel', () {
late ProviderContainer container;
late MockInternetConnectionManager mockInternetConnectionManager;
late MockAsyncListener listener;

setUp(() {
container = ProviderContainer();
mockInternetConnectionManager = MockInternetConnectionManager();
container = ProviderContainer(overrides: [
internetConnectionManagerProvider
.overrideWithValue(mockInternetConnectionManager),
]);

listener = MockAsyncListener();
container.listen(
loginViewModelProvider,
Expand Down Expand Up @@ -76,9 +83,30 @@ void main() {
});
});

// TODO: Update when integrating with API
group('login', () {
test('When logging in unsuccessfully, it emits error correspondingly',
// test('When logging in unsuccessfully, it emits error correspondingly',
// () async {
// const data = AsyncData<void>(null);
// // verify initial value from build method
// verify(listener(null, data));

// final loginViewModel = container.read(loginViewModelProvider.notifier);
// await loginViewModel.login(
// email: '[email protected]', password: '12345678');

// verifyInOrder([
// listener(data, isA<AsyncLoading>()),
// listener(isA<AsyncLoading<void>>(), isA<AsyncError<void>>()),
// ]);
// verifyNoMoreInteractions(listener);
// });

test(
'When logging in without Internet connection, it emits error correspondingly',
() async {
when(mockInternetConnectionManager.hasConnection())
.thenAnswer((_) async => false);
const data = AsyncData<void>(null);
// verify initial value from build method
verify(listener(null, data));
Expand Down

0 comments on commit 6b4275d

Please sign in to comment.