From 3efb61a8dc74452d881e84dfd2857e658a0a81df Mon Sep 17 00:00:00 2001 From: fluttermiddlepodcast Date: Wed, 18 Feb 2026 12:05:39 +0300 Subject: [PATCH 1/4] added either (dartz) and sealed class-based error handlings --- lib/core/network/failure.dart | 12 ++ lib/core/network/result.dart | 29 ++++ lib/features/users/bloc/users_bloc.dart | 141 ++++++++++++++++++ .../users/repository/users_repository.dart | 19 +++ .../repository/users_repository_impl.dart | 74 +++++++++ pubspec.lock | 8 + pubspec.yaml | 1 + 7 files changed, 284 insertions(+) create mode 100644 lib/core/network/failure.dart create mode 100644 lib/core/network/result.dart diff --git a/lib/core/network/failure.dart b/lib/core/network/failure.dart new file mode 100644 index 0000000..ebc4e8b --- /dev/null +++ b/lib/core/network/failure.dart @@ -0,0 +1,12 @@ +sealed class Failure { + const Failure(); +} + +final class SomeFailure extends Failure { + final String error; + + const SomeFailure(this.error); + + @override + String toString() => error; +} diff --git a/lib/core/network/result.dart b/lib/core/network/result.dart new file mode 100644 index 0000000..c3cfc9f --- /dev/null +++ b/lib/core/network/result.dart @@ -0,0 +1,29 @@ +import 'package:bloc_example/core/network/failure.dart'; + +sealed class Result { + const Result(); + + factory Result.ok(T value) = Ok; + + factory Result.err(Failure failure) = Err; + + R fold({ + required R Function(Failure) onErr, + required R Function(T) onOk, + }) => switch (this) { + Err(:final failure) => onErr(failure), + Ok(:final value) => onOk(value), + }; +} + +final class Ok extends Result { + final T value; + + const Ok(this.value); +} + +final class Err extends Result { + final Failure failure; + + const Err(this.failure); +} diff --git a/lib/features/users/bloc/users_bloc.dart b/lib/features/users/bloc/users_bloc.dart index 4f2f4c6..e79960d 100644 --- a/lib/features/users/bloc/users_bloc.dart +++ b/lib/features/users/bloc/users_bloc.dart @@ -1,5 +1,7 @@ import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:bloc_example/core/bloc/transformers/measure_time.dart'; +import 'package:bloc_example/core/network/failure.dart'; +import 'package:bloc_example/core/network/result.dart'; import 'package:bloc_example/features/users/bloc/users_bloc_event.dart'; import 'package:bloc_example/features/users/bloc/users_bloc_state.dart'; import 'package:bloc_example/features/users/model/user.dart'; @@ -27,6 +29,7 @@ class UsersBloc extends HydratedBloc { } } + // --- RECORDS --- Future _onFetch( UsersBlocEventFetch event, Emitter emit, @@ -35,6 +38,7 @@ class UsersBloc extends HydratedBloc { limit: 30, page: 0, ); + if (usersRes.$2 == null) { final users = usersRes.$1!; @@ -50,6 +54,57 @@ class UsersBloc extends HydratedBloc { } } + // --- EITHER (dartz) --- + // Future _onFetch( + // UsersBlocEventFetch event, + // Emitter emit, + // ) async { + // final usersRes = await usersRepository.fetchUsers( + // limit: 30, + // page: 0, + // ); + + // usersRes.fold( + // (error) => emit(UsersBlocStateError(error)), + // (users) { + // emit( + // UsersBlocStateLoaded( + // users: users, + // canLoadMore: users.length == 30, + // page: 1, + // ), + // ); + // }, + // ); + // } + + // --- SEALED CLASSES --- + // Future _onFetch( + // UsersBlocEventFetch event, + // Emitter emit, + // ) async { + // final usersRes = await usersRepository.fetchUsers( + // limit: 30, + // page: 0, + // ); + + // usersRes.fold( + // onErr: (failure) => emit( + // UsersBlocStateError( + // failure.toString(), + // ), + // ), + // onOk: (users) => emit( + // UsersBlocStateLoaded( + // users: users, + // canLoadMore: users.length == 30, + // page: 1, + // ), + // ), + // ); + // } + + // --- RECORDS --- Future _onFetchMore( UsersBlocEventFetchMore event, Emitter emit, @@ -70,6 +125,7 @@ class UsersBloc extends HydratedBloc { limit: 30, page: page, ); + if (usersRes.$2 == null) { final users = usersRes.$1!; @@ -88,6 +144,91 @@ class UsersBloc extends HydratedBloc { } } + // --- EITHER (dartz) --- + // Future _onFetchMore( + // UsersBlocEventFetchMore event, + // Emitter emit, + // ) async { + // if (state is! UsersBlocStateLoaded) { + // return; + // } + + // final currState = state as UsersBlocStateLoaded; + + // if (!currState.canLoadMore) { + // return; + // } + + // final page = currState.page; + + // final usersRes = await usersRepository.fetchUsers( + // limit: 30, + // page: page, + // ); + + // usersRes.fold( + // (error) => emit(UsersBlocStateError(error)), + // (users) { + // emit( + // UsersBlocStateLoaded( + // users: [ + // ...currState.users, + // ...users, + // ], + // canLoadMore: users.length == 30, + // page: page + 1, + // ), + // ); + // }, + // ); + // } + + // --- SEALED CLASSES --- + // Future _onFetchMore( + // UsersBlocEventFetchMore event, + // Emitter emit, + // ) async { + // if (state is! UsersBlocStateLoaded) { + // return; + // } + + // final currState = state as UsersBlocStateLoaded; + + // if (!currState.canLoadMore) { + // return; + // } + + // final page = currState.page; + + // final usersRes = await usersRepository.fetchUsers( + // limit: 30, + // page: page, + // ); + + // switch (usersRes) { + // case Ok>(:final value): + // emit( + // UsersBlocStateLoaded( + // users: [ + // ...currState.users, + // ...value, + // ], + // canLoadMore: value.length == 30, + // page: page + 1, + // ), + // ); + // case Err>(:final failure): + // switch (failure) { + // case SomeFailure(:final error): + // emit( + // UsersBlocStateError( + // error, + // ), + // ); + // } + // } + // } + Future _onRefresh( UsersBlocEventRefresh event, Emitter emit, diff --git a/lib/features/users/repository/users_repository.dart b/lib/features/users/repository/users_repository.dart index e5e0213..7338641 100644 --- a/lib/features/users/repository/users_repository.dart +++ b/lib/features/users/repository/users_repository.dart @@ -1,8 +1,27 @@ +import 'package:bloc_example/core/network/result.dart'; import 'package:bloc_example/features/users/model/user.dart'; +import 'package:dartz/dartz.dart'; +// --- RECORDS --- abstract class UsersRepository { Future<(List?, String?)> fetchUsers({ int limit, int page, }); } + +// --- EITHER (dartz) --- +// abstract class UsersRepository { +// Future>> fetchUsers({ +// int limit, +// int page, +// }); +// } + +// --- SEALED CLASSES --- +// abstract class UsersRepository { +// Future>> fetchUsers({ +// int limit, +// int page, +// }); +// } diff --git a/lib/features/users/repository/users_repository_impl.dart b/lib/features/users/repository/users_repository_impl.dart index 7b140cf..135956e 100644 --- a/lib/features/users/repository/users_repository_impl.dart +++ b/lib/features/users/repository/users_repository_impl.dart @@ -1,9 +1,13 @@ import 'dart:io'; +import 'package:bloc_example/core/network/failure.dart'; +import 'package:bloc_example/core/network/result.dart'; import 'package:bloc_example/features/users/model/user.dart'; import 'package:bloc_example/features/users/repository/users_repository.dart'; +import 'package:dartz/dartz.dart'; import 'package:dio/dio.dart'; +// --- RECORDS --- class UsersRepositoryImpl extends UsersRepository { final Dio dio; @@ -35,3 +39,73 @@ class UsersRepositoryImpl extends UsersRepository { } } } + +// --- EITHER (dartz) --- +// class UsersRepositoryImpl extends UsersRepository { +// final Dio dio; + +// UsersRepositoryImpl(this.dio); + +// @override +// Future>> fetchUsers({ +// int limit = 30, +// int page = 0, +// }) async { +// try { +// final res = await dio.get('?page=$page&results=$limit'); +// if (res.statusCode == HttpStatus.ok) { +// final rawUsers = res.data['results'] as List; +// final users = rawUsers.map((rawUser) => User.fromJson(rawUser)).toList(); + +// return Right(users); +// } + +// return Left( +// '''Network error: +// - status code: ${res.statusCode} +// - data: ${res.data} +// ''', +// ); +// } catch (e) { +// return Left('Network error: $e'); +// } +// } +// } + +// --- SEALED CLASSES --- +// class UsersRepositoryImpl extends UsersRepository { +// final Dio dio; + +// UsersRepositoryImpl(this.dio); + +// @override +// Future>> fetchUsers({ +// int limit = 30, +// int page = 0, +// }) async { +// try { +// final res = await dio.get('?page=$page&results=$limit'); +// if (res.statusCode == HttpStatus.ok) { +// final rawUsers = res.data['results'] as List; +// final users = rawUsers.map((rawUser) => User.fromJson(rawUser)).toList(); + +// return Result.ok(users); +// } + +// return Result.err( +// SomeFailure( +// '''Network error: +// - status code: ${res.statusCode} +// - data: ${res.data} +// ''', +// ), +// ); +// } catch (e) { +// return Result.err( +// SomeFailure( +// 'Network error: $e', +// ), +// ); +// } +// } +// } diff --git a/pubspec.lock b/pubspec.lock index 19d00fe..30376c2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -209,6 +209,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + dartz: + dependency: "direct main" + description: + name: dartz + sha256: e6acf34ad2e31b1eb00948692468c30ab48ac8250e0f0df661e29f12dd252168 + url: "https://pub.dev" + source: hosted + version: "0.10.1" diff_match_patch: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b543dee..6abafd3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: rxdart: 0.28.0 collection: + dartz: dev_dependencies: flutter_test: From eab445c58bc6711572786be60a26d9748f5ee373 Mon Sep 17 00:00:00 2001 From: fluttermiddlepodcast Date: Sun, 22 Feb 2026 19:18:47 +0300 Subject: [PATCH 2/4] some few improvements --- lib/features/users/bloc/users_bloc.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/features/users/bloc/users_bloc.dart b/lib/features/users/bloc/users_bloc.dart index e79960d..d8939d2 100644 --- a/lib/features/users/bloc/users_bloc.dart +++ b/lib/features/users/bloc/users_bloc.dart @@ -205,19 +205,20 @@ class UsersBloc extends HydratedBloc { // page: page, // ); - // switch (usersRes) { - // case Ok>(:final value): + // usersRes.fold( + // onOk: (users) { // emit( // UsersBlocStateLoaded( // users: [ // ...currState.users, - // ...value, + // ...users, // ], - // canLoadMore: value.length == 30, + // canLoadMore: users.length == 30, // page: page + 1, // ), // ); - // case Err>(:final failure): + // }, + // onErr: (failure) { // switch (failure) { // case SomeFailure(:final error): // emit( @@ -226,7 +227,8 @@ class UsersBloc extends HydratedBloc { // ), // ); // } - // } + // }, + // ); // } Future _onRefresh( From f78fc0ce7a4feafa405dc6efccea23438f168816 Mon Sep 17 00:00:00 2001 From: fluttermiddlepodcast Date: Sun, 22 Feb 2026 20:34:59 +0300 Subject: [PATCH 3/4] some few improvements --- lib/features/users/bloc/users_bloc.dart | 12 ++- .../users/repository/users_repository.dart | 3 +- .../repository/users_repository_impl.dart | 74 ++++++++++--------- 3 files changed, 52 insertions(+), 37 deletions(-) diff --git a/lib/features/users/bloc/users_bloc.dart b/lib/features/users/bloc/users_bloc.dart index d8939d2..344aa2a 100644 --- a/lib/features/users/bloc/users_bloc.dart +++ b/lib/features/users/bloc/users_bloc.dart @@ -65,7 +65,11 @@ class UsersBloc extends HydratedBloc { // ); // usersRes.fold( - // (error) => emit(UsersBlocStateError(error)), + // (error) => emit( + // UsersBlocStateError( + // error.toString(), + // ), + // ), // (users) { // emit( // UsersBlocStateLoaded( @@ -167,7 +171,11 @@ class UsersBloc extends HydratedBloc { // ); // usersRes.fold( - // (error) => emit(UsersBlocStateError(error)), + // (error) => emit( + // UsersBlocStateError( + // error.toString(), + // ), + // ), // (users) { // emit( // UsersBlocStateLoaded( diff --git a/lib/features/users/repository/users_repository.dart b/lib/features/users/repository/users_repository.dart index 7338641..40707f9 100644 --- a/lib/features/users/repository/users_repository.dart +++ b/lib/features/users/repository/users_repository.dart @@ -1,3 +1,4 @@ +import 'package:bloc_example/core/network/failure.dart'; import 'package:bloc_example/core/network/result.dart'; import 'package:bloc_example/features/users/model/user.dart'; import 'package:dartz/dartz.dart'; @@ -12,7 +13,7 @@ abstract class UsersRepository { // --- EITHER (dartz) --- // abstract class UsersRepository { -// Future>> fetchUsers({ +// Future>> fetchUsers({ // int limit, // int page, // }); diff --git a/lib/features/users/repository/users_repository_impl.dart b/lib/features/users/repository/users_repository_impl.dart index 135956e..4f84a42 100644 --- a/lib/features/users/repository/users_repository_impl.dart +++ b/lib/features/users/repository/users_repository_impl.dart @@ -8,37 +8,37 @@ import 'package:dartz/dartz.dart'; import 'package:dio/dio.dart'; // --- RECORDS --- -class UsersRepositoryImpl extends UsersRepository { - final Dio dio; - - UsersRepositoryImpl(this.dio); - - @override - Future<(List?, String?)> fetchUsers({ - int limit = 30, - int page = 0, - }) async { - try { - final res = await dio.get('?page=$page&results=$limit'); - if (res.statusCode == HttpStatus.ok) { - final rawUsers = res.data['results'] as List; - final users = rawUsers.map((rawUser) => User.fromJson(rawUser)).toList(); - - return (users, null); - } - - return ( - null, - '''Network error: - - status code: ${res.statusCode} - - data: ${res.data} - ''', - ); - } catch (e) { - return (null, 'Network error: $e'); - } - } -} +// class UsersRepositoryImpl extends UsersRepository { +// final Dio dio; + +// UsersRepositoryImpl(this.dio); + +// @override +// Future<(List?, String?)> fetchUsers({ +// int limit = 30, +// int page = 0, +// }) async { +// try { +// final res = await dio.get('?page=$page&results=$limit'); +// if (res.statusCode == HttpStatus.ok) { +// final rawUsers = res.data['results'] as List; +// final users = rawUsers.map((rawUser) => User.fromJson(rawUser)).toList(); + +// return (users, null); +// } + +// return ( +// null, +// '''Network error: +// - status code: ${res.statusCode} +// - data: ${res.data} +// ''', +// ); +// } catch (e) { +// return (null, 'Network error: $e'); +// } +// } +// } // --- EITHER (dartz) --- // class UsersRepositoryImpl extends UsersRepository { @@ -47,7 +47,7 @@ class UsersRepositoryImpl extends UsersRepository { // UsersRepositoryImpl(this.dio); // @override -// Future>> fetchUsers({ +// Future>> fetchUsers({ // int limit = 30, // int page = 0, // }) async { @@ -61,13 +61,19 @@ class UsersRepositoryImpl extends UsersRepository { // } // return Left( -// '''Network error: +// SomeFailure( +// '''Network error: // - status code: ${res.statusCode} // - data: ${res.data} // ''', +// ), // ); // } catch (e) { -// return Left('Network error: $e'); +// return Left( +// SomeFailure( +// 'Network error: $e', +// ), +// ); // } // } // } From c99e32a3670888c4fc2830e84c37a92705139eff Mon Sep 17 00:00:00 2001 From: fluttermiddlepodcast Date: Sun, 22 Feb 2026 20:35:19 +0300 Subject: [PATCH 4/4] some few improvements --- .../repository/users_repository_impl.dart | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/lib/features/users/repository/users_repository_impl.dart b/lib/features/users/repository/users_repository_impl.dart index 4f84a42..bfd6545 100644 --- a/lib/features/users/repository/users_repository_impl.dart +++ b/lib/features/users/repository/users_repository_impl.dart @@ -8,37 +8,37 @@ import 'package:dartz/dartz.dart'; import 'package:dio/dio.dart'; // --- RECORDS --- -// class UsersRepositoryImpl extends UsersRepository { -// final Dio dio; - -// UsersRepositoryImpl(this.dio); - -// @override -// Future<(List?, String?)> fetchUsers({ -// int limit = 30, -// int page = 0, -// }) async { -// try { -// final res = await dio.get('?page=$page&results=$limit'); -// if (res.statusCode == HttpStatus.ok) { -// final rawUsers = res.data['results'] as List; -// final users = rawUsers.map((rawUser) => User.fromJson(rawUser)).toList(); - -// return (users, null); -// } - -// return ( -// null, -// '''Network error: -// - status code: ${res.statusCode} -// - data: ${res.data} -// ''', -// ); -// } catch (e) { -// return (null, 'Network error: $e'); -// } -// } -// } +class UsersRepositoryImpl extends UsersRepository { + final Dio dio; + + UsersRepositoryImpl(this.dio); + + @override + Future<(List?, String?)> fetchUsers({ + int limit = 30, + int page = 0, + }) async { + try { + final res = await dio.get('?page=$page&results=$limit'); + if (res.statusCode == HttpStatus.ok) { + final rawUsers = res.data['results'] as List; + final users = rawUsers.map((rawUser) => User.fromJson(rawUser)).toList(); + + return (users, null); + } + + return ( + null, + '''Network error: + - status code: ${res.statusCode} + - data: ${res.data} + ''', + ); + } catch (e) { + return (null, 'Network error: $e'); + } + } +} // --- EITHER (dartz) --- // class UsersRepositoryImpl extends UsersRepository {