Разработка мобильного приложения с использованием Flutter и принципов чистой архитектуры (Clean Architecture) предполагает разделение приложения на слои: Presentation (представление), Domain (доменная логика) и Data (данные). Интеграция flutter_bloc
для управления состоянием поможет организовать слой Presentation более эффективно и поддерживаемо.
Вот пошаговое руководство по внедрению flutter_bloc
в ваше приложение с чистой архитектурой:
1. Установка flutter_bloc
Добавьте flutter_bloc
и equatable
в ваш pubspec.yaml
:
dependencies:
flutter_bloc: ^8.1.0
equatable: ^2.0.3
Затем выполните команду:
flutter pub get
2. Организация слоев чистой архитектуры
- Presentation Layer (Слой представления): UI-компоненты и управление состоянием (Bloc/Cubit).
- Domain Layer (Доменный слой): Бизнес-логика, сущности и use cases.
- Data Layer (Слой данных): Репозитории и источники данных (API, базы данных).
3. Создание Use Cases в Domain Layer
Пример use case для получения профиля пользователя:
class GetUserProfile {
final UserRepository repository;
GetUserProfile(this.repository);
Future<Either<Failure, User>> call(int userId) {
return repository.getUserProfile(userId);
}
}
4. Реализация репозитория в Data Layer
Создайте интерфейс репозитория в Domain Layer:
abstract class UserRepository {
Future<Either<Failure, User>> getUserProfile(int userId);
}
И его реализацию в Data Layer:
class UserRepositoryImpl implements UserRepository {
final UserRemoteDataSource remoteDataSource;
UserRepositoryImpl(this.remoteDataSource);
@override
Future<Either<Failure, User>> getUserProfile(int userId) async {
try {
final user = await remoteDataSource.fetchUserProfile(userId);
return Right(user);
} catch (e) {
return Left(ServerFailure());
}
}
}
5. Создание Bloc в Presentation Layer
Bloc будет управлять состоянием UI, взаимодействуя с Use Cases из Domain Layer.
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
part 'user_event.dart';
part 'user_state.dart';
class UserBloc extends Bloc<UserEvent, UserState> {
final GetUserProfile getUserProfile;
UserBloc(this.getUserProfile) : super(UserInitial()) {
on<LoadUserEvent>(_onLoadUserEvent);
}
Future<void> _onLoadUserEvent(
LoadUserEvent event, Emitter<UserState> emit) async {
emit(UserLoading());
final result = await getUserProfile(event.userId);
result.fold(
(failure) => emit(UserError('Ошибка загрузки данных')),
(user) => emit(UserLoaded(user)),
);
}
}
user_event.dart:
part of 'user_bloc.dart';
abstract class UserEvent extends Equatable {
const UserEvent();
@override
List<Object> get props => [];
}
class LoadUserEvent extends UserEvent {
final int userId;
const LoadUserEvent(this.userId);
@override
List<Object> get props => [userId];
}
user_state.dart:
part of 'user_bloc.dart';
abstract class UserState extends Equatable {
const UserState();
@override
List<Object?> get props => [];
}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final User user;
const UserLoaded(this.user);
@override
List<Object?> get props => [user];
}
class UserError extends UserState {
final String message;
const UserError(this.message);
@override
List<Object?> get props => [message];
}
6. Интеграция Bloc в UI
В вашем виджете используйте BlocProvider
для предоставления экземпляра UserBloc
:
class UserProfilePage extends StatelessWidget {
final int userId;
const UserProfilePage({Key? key, required this.userId}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => UserBloc(GetUserProfile(context.read<UserRepository>())),
child: UserProfileView(userId: userId),
);
}
}
7. Реализация UI с использованием BlocBuilder
class UserProfileView extends StatelessWidget {
final int userId;
const UserProfileView({Key? key, required this.userId}) : super(key: key);
@override
Widget build(BuildContext context) {
// Запускаем событие загрузки профиля пользователя
context.read<UserBloc>().add(LoadUserEvent(userId));
return Scaffold(
appBar: AppBar(title: Text('Профиль пользователя')),
body: BlocBuilder<UserBloc, UserState>(
builder: (context, state) {
if (state is UserLoading || state is UserInitial) {
return Center(child: CircularProgressIndicator());
} else if (state is UserLoaded) {
return Center(child: Text('Привет, ${state.user.name}'));
} else if (state is UserError) {
return Center(child: Text(state.message));
} else {
return SizedBox.shrink();
}
},
),
);
}
}
8. Инъекция зависимостей
Для соблюдения принципов чистой архитектуры важно правильно организовать инъекцию зависимостей. Используйте пакет get_it
для управления зависимостями.
Настройка сервис-локатора (service_locator.dart
):
import 'package:get_it/get_it.dart';
final sl = GetIt.instance;
void init() {
// Блоки
sl.registerFactory(() => UserBloc(sl()));
// Use Cases
sl.registerLazySingleton(() => GetUserProfile(sl()));
// Репозитории
sl.registerLazySingleton<UserRepository>(() => UserRepositoryImpl(sl()));
// Источники данных
sl.registerLazySingleton<UserRemoteDataSource>(() => UserRemoteDataSourceImpl());
}
9. Инициализация сервис-локатора при запуске приложения
void main() {
init();
runApp(MyApp());
}
10. Обновление BlocProvider для использования сервис-локатора
BlocProvider(
create: (_) => sl<UserBloc>(),
child: UserProfileView(userId: userId),
);
Итог
Интеграция flutter_bloc
в приложение с чистой архитектурой включает следующие шаги:
- Presentation Layer: Использование Bloc для управления состоянием UI.
- Domain Layer: Реализация Use Cases для бизнес-логики.
- Data Layer: Создание репозиториев и источников данных для получения и хранения информации.
Это обеспечивает четкое разделение ответственности и облегчает тестирование и поддержку приложения.
Рекомендации
- Разделение зависимостей: Избегайте прямых зависимостей между Presentation и Data слоями.
- Тестирование: Пишите модульные тесты для Bloc и Use Cases.
- Обработка ошибок: Реализуйте надлежащую обработку исключений и ошибок в репозиториях и Use Cases.
- Обновление состояний: Используйте
Equatable
для оптимизации производительности при обновлении состояний.
Посмотреть подходящие вакансии для Flutter-разработчика можно тут!