Используем flutter_bloc для state management.

  • 25 сентября 2024
  • 31 просмотр
  • 0 комментариев

Разработка мобильного приложения с использованием 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-разработчика можно тут!

    Оставьте отзыв
    (минимум 60 знаков)
    Оценка5/5
    Нужно авторизоваться