Skip to content

Commit 4d8ef1c

Browse files
committed
refactored error handling
1 parent 8931943 commit 4d8ef1c

File tree

6 files changed

+80
-33
lines changed

6 files changed

+80
-33
lines changed

lib/model/error_handling/result.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
typedef Result<T> = ({T? data, dynamic error});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import 'package:flutter_template/model/error_handling/result.dart';
2+
import 'package:icapps_architecture/icapps_architecture.dart';
3+
4+
mixin ErrorHandlingRepositoryMixin {
5+
Future<Result<T>> fetch<T>({
6+
required Future<T> Function() function,
7+
required String errorMessage,
8+
}) async {
9+
T? data;
10+
dynamic error;
11+
try {
12+
data = await function();
13+
} catch (e) {
14+
error = e;
15+
staticLogger.e(errorMessage, error: error);
16+
}
17+
18+
return (data: data, error: error);
19+
}
20+
}

lib/repository/todo/todo_repository.dart

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import 'dart:async';
2+
13
import 'package:flutter_template/database/todo/todo_dao_storage.dart';
4+
import 'package:flutter_template/model/error_handling/result.dart';
25
import 'package:flutter_template/model/webservice/todo/todo.dart';
6+
import 'package:flutter_template/repository/error_handling/error_handling_repository_mixin.dart';
37
import 'package:flutter_template/webservice/todo/todo_service.dart';
48
import 'package:injectable/injectable.dart';
59

@@ -10,14 +14,14 @@ abstract class TodoRepository {
1014

1115
Stream<List<Todo>> getTodos();
1216

13-
Future<List<Todo>> fetchTodos();
17+
Future<Result<List<Todo>>> fetchTodos();
1418

1519
Future<void> saveTodo(String todo);
1620

1721
Future<void> setTodoState({required int id, required bool value});
1822
}
1923

20-
class _TodoRepository implements TodoRepository {
24+
class _TodoRepository with ErrorHandlingRepositoryMixin implements TodoRepository {
2125
final TodoDaoStorage _todoDao;
2226
final TodoService _todoService;
2327

@@ -29,13 +33,16 @@ class _TodoRepository implements TodoRepository {
2933
///Get Users will be requested from
3034
///https://jsonplaceholder.typicode.com/todos
3135
@override
32-
Future<List<Todo>> fetchTodos() async {
33-
final results = await _todoService.getTodos();
34-
for (final todo in results) {
35-
await _todoDao.createTodoWithValue(todo);
36-
}
37-
return results;
38-
}
36+
Future<Result<List<Todo>>> fetchTodos() => fetch(
37+
errorMessage: 'Failed to fetch todos',
38+
function: () async {
39+
final data = await _todoService.getTodos();
40+
for (final todo in data) {
41+
await _todoDao.createTodoWithValue(todo);
42+
}
43+
return data;
44+
},
45+
);
3946

4047
@override
4148
Future<void> saveTodo(String todo) async {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:flutter_template/di/injectable.dart';
3+
import 'package:flutter_template/model/error_handling/result.dart';
4+
import 'package:flutter_template/navigator/main_navigator.dart';
5+
import 'package:icapps_architecture/icapps_architecture.dart';
6+
7+
mixin ErrorHandlingViewModelMixin on ChangeNotifierEx {
8+
MainNavigator get navigator => getIt();
9+
10+
Future<T?> failableFuture<T>(
11+
Future<Result<T>> Function() future, {
12+
ValueChanged<dynamic>? onError,
13+
ValueChanged<bool>? onLoadingChanged,
14+
}) async {
15+
onLoadingChanged?.call(true);
16+
notifyListeners();
17+
final result = await future();
18+
if (result.error != null) {
19+
navigator.showError(result);
20+
onError?.call(result.error);
21+
return null;
22+
}
23+
onLoadingChanged?.call(false);
24+
if (disposed) return null;
25+
notifyListeners();
26+
return result.data;
27+
}
28+
}

lib/viewmodel/todo/todo_list/todo_list_viewmodel.dart

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ import 'package:flutter_template/model/webservice/todo/todo.dart';
22
import 'package:flutter_template/navigator/main_navigator.dart';
33
import 'package:flutter_template/repository/todo/todo_repository.dart';
44
import 'package:flutter_template/util/locale/localization_keys.dart';
5+
import 'package:flutter_template/viewmodel/mixin/error_handling_mixin.dart';
56
import 'package:icapps_architecture/icapps_architecture.dart';
67
import 'package:injectable/injectable.dart';
78

89
@injectable
9-
class TodoListViewModel with ChangeNotifierEx {
10+
class TodoListViewModel with ChangeNotifierEx, ErrorHandlingViewModelMixin {
1011
final MainNavigator _navigator;
1112
final TodoRepository _todoRepo;
1213

1314
late Stream<List<Todo>> _todoStream;
1415

1516
var _isLoading = false;
17+
1618
String? _errorKey;
1719

1820
bool get isLoading => _isLoading;
@@ -30,24 +32,13 @@ class TodoListViewModel with ChangeNotifierEx {
3032
_todoStream = _todoRepo.getTodos();
3133
}
3234

33-
Future<void> onDownloadClicked() async {
34-
try {
35-
_isLoading = true;
36-
_errorKey = null;
37-
notifyListeners();
38-
await _todoRepo.fetchTodos();
39-
} catch (e, stack) {
40-
logger.error('failed to get todos', error: e, trace: stack);
41-
if (e is LocalizedError) {
42-
_errorKey = e.getLocalizedKey();
43-
} else {
44-
_errorKey = LocalizationKeys.errorGeneral;
45-
}
46-
} finally {
47-
_isLoading = false;
48-
notifyListeners();
49-
}
50-
}
35+
Future<void> onDownloadClicked() => failableFuture(
36+
_todoRepo.fetchTodos,
37+
onError: (error) => _errorKey = _getErrorKeyFromError(error),
38+
onLoadingChanged: (isLoading) => _isLoading = isLoading,
39+
);
40+
41+
String _getErrorKeyFromError(dynamic error) => error is LocalizedError ? error.getLocalizedKey() : LocalizationKeys.errorGeneral;
5142

5243
Future<void> onTodoChanged({required int? id, required bool value}) async {
5344
if (id == null) return;

test/repository/todo/todo_repository_test.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ void main() {
5353
group('fetchTodos', () {
5454
test('fetchTodos', () async {
5555
when(todoService.getTodos()).thenAnswer((_) async => []);
56-
final data = await sut.fetchTodos();
57-
expect(data.isEmpty, true);
56+
final result = await sut.fetchTodos();
57+
expect(result.data?.isEmpty, true);
5858
verify(todoService.getTodos()).calledOnce();
5959
verifyNoMoreInteractions(todoService);
6060
verifyZeroInteractions(todoDao);
@@ -64,9 +64,9 @@ void main() {
6464
const Todo(id: 1, title: '2', completed: true),
6565
const Todo(id: 3, title: '3', completed: false),
6666
]);
67-
final data = await sut.fetchTodos();
68-
expect(data.isNotEmpty, true);
69-
expect(data.length, 2);
67+
final result = await sut.fetchTodos();
68+
expect(result.data?.isNotEmpty, true);
69+
expect(result.data?.length, 2);
7070
verify(todoService.getTodos()).calledOnce();
7171
verify((todoDao as MockTodoDaoStorage).createTodoWithValue(any)).calledTwice();
7272
});

0 commit comments

Comments
 (0)