Как стать автором
Поиск
Написать публикацию
Обновить

Архитектура Flutter проекта простым языком. Clean Arch (MVVM, DI, Bloc, Inversion of Control)

Уровень сложностиСредний
Время на прочтение7 мин
Количество просмотров7.4K

Для чего нужна архитектура?

В мире программирования наличие общепринятой архитектуры проекта имеет решающее значение для успешного выполнения и поддержания проекта. Архитектура определяет структуру, взаимосвязь между компонентами и их взаимодействия. Четко разработанная архитектура не только упрощает процесс разработки, но и обеспечивает масштабируемость, гибкость и удобство сопровождения кода. В конечном итоге, архитектурное согласие в команде повышает производительность, качество конечного продукта и снижает затраты на разработку и поддержку.

Clean Architecture

Clean Architecture является одной из самых популярных архитектур в мире разработки программного обеспечения, особенно для крупных и средних проектов. Она разработана Робертом Мартином (Uncle Bob) и получила широкое распространение благодаря своей модульности, гибкости и легкости в сопровождении.

Почему же она завоевала такую популярность и симпатию среди Flutter разработчиков и не только? Вот несколько причин, почему эта архитектура столь популярна и эффективна:

Четкое разделение ответственности на слои:

  • Презентационный слой (Presentation): Отвечает за пользовательский интерфейс и взаимодействие с пользователем. Здесь находятся виджеты Flutter, которые отображают данные и обрабатывают пользовательские действия. Кроме интерфейса в текущем слое находится наш менеджер состояния и view модель.

  • Слой домена (Domain): Содержит бизнес-логику и правила приложения. Это сердцевина нашей фичи или приложения, которая независима от фреймворков и внешних библиотек.

  • Слой данных (Data): Отвечает за управление данными, включая работу с базами данных, сетевыми запросами и кешированием.

Позже мы разберем где и какие именно папки, классы и их реализации должны находится.

Какие шаблоны проектирования нужны для чистой архитектуры?

MVVM - Model View View Model - это архитектурный паттерн, который используется для разделения логики представления (UI) и бизнес-логики в приложениях, стоит использовать только в том случае если в нашем виджете , который должен быть максимально “глупым”, существует зависимости от репозитория или же методов. Если присутствует большое количество переменных, переопределенных методов по типу initState так же нужно создать viewModel, в которую мы перенесем всю ненужную "глупому" виджету логику. Ниже нарисованная схема поможет лучше понять смысл этого паттерна.

Inversion of Control (инверсия управления) - это некий абстрактный принцип, набор рекомендаций для написания слабо связанного кода. Суть которого в том, что каждый компонент системы должен быть как можно более изолированным от других, не полагаясь в своей работе на детали конкретной реализации других компонентов.

Dependency Injection (внедрение зависимостей) - это одна из реализаций этого принципа помимо этого есть еще: Service Locator - широко известный анти-шаблон. А чем он занимается? Предоставляет доступ одних объектов к другим объектам.

Принципы ViewModel

В каждом проекте вы неизбежно столкнетесь с использованием ViewModel. Понимание этой концепции часто вызывает затруднения у начинающих разработчиков. Поэтому не будет лишним её разобрать.

Model - Модель представляет данные и бизнес-логику приложения. Она отвечает за управление данными и обеспечение согласованности и целостности приложения. В нашем случае Model будет абстрактным репозиторием из Domain слоя.
View - View отвечает за представление данных пользователю и захват пользовательских взаимодействий. Это пользовательский интерфейс, с которым взаимодействуют пользователи. Сохраняйте виджеты максимально «глупыми», минимизируя логику в компонентах пользовательского интерфейса вынося их в ViewModel.
ViewModel - это посредник между Model и View. Он содержит логику представления, раскрывая данные и команды, к которым View может привязываться. ViewModel разработан для тестирования независимо от UI. Он также часто инкапсулирует состояние View и обрабатывает ввод и взаимодействие пользователя.

Пример ViewModel:

class MapViewModel {
  MapViewModel({required this.context,required this.infoLocationRepository,required this.locationRepository});
  final BuildContext context;
  final LocationRepository locationRepository;
  final InfoLocationRepository infoLocationRepository;
  
  late final UserLocationBloc locationBloc = UserLocationBloc(repository: locationRepository);
  late final UserInfoLocationBloc userInfoLocationBloc = UserInfoLocationBloc(infoLocationRepository: infoLocationRepository);
  late GoogleMapController mapController;
  
  ValueNotifier selectLat = ValueNotifier([double]);
  ValueNotifier selectLng = ValueNotifier([double]);
  String styleMap = '';
  get onMapCreated => _onMapCreated;
  get initialize => _initialize;

  void _onMapCreated(GoogleMapController controller) {
    mapController = controller;
  }

  void _initialize() {
    locationBloc.add(GetStartUserLocation());
    DefaultAssetBundle.of(context).loadString('style.json').then((onValue) {
      styleMap = onValue;
    });
  }
}

Теперь наш виджет по настоящему "глупый", в нем нет реализации методов и прочего, теперь это все в нашей view model.

Структура проекта:

Utils

Utils содержит в себе все настройки и коллекции приложения. Тема, цвета нашего приложения. Коллекции с нашими svg/png картинками. Какая-то часть разработчиков называют данную папку theme.

Features

Features папка будет содержать все функции приложения, такие как аутентификация, профиль… А уже каждая фича будет разделяться на слои:

Data Layer

Уровень данных отвечает за взаимодействие с внешними источниками данных, такими как базы данных, сетевые службы или репозитории. Он управляет хранением и извлечением данных.

  • Repositories - Здесь хранится вся реализация бизнес логики, которая имплементируется от абстрактных классов описанных в Domain.

  • Models - Модельки наших ответов с сервера.

  • Service - Тут хранится реализация логики обращения к API или же к БД. В моем случае я использую библиотеку Retrofit, которая не нуждается в обработке и долгой реализации, именно поэтому наш абстрактный класс будет реализован сразу в Data. Как вы видите благодаря многим инструментам мы можем отойти от строгости структурирования чистой архитектуры.

Domain Layer

Domain Layer, также известный как Business Logic или Use Case Layer, содержит основные бизнес-правила и логику приложения. Он представляет собой сердце программной системы, инкапсулируя существенную функциональность, которая не зависит от какой-либо конкретной структуры.

  • Repositories - Здесь мы объявляем наши абстрактные классы, которые реализованы в data.

  • Entity - наши сущности, отличаются от моделей тем, что модельки используются для того, чтобы распарсить данные, а сущности для того чтобы использовать их.

  • Use_case - часто реализуются в виде отдельных классов или методов, которые инкапсулируют бизнес-логику. Это позволяет отделить бизнес-логику от деталей реализации (интерфейсы пользователя, базы данных и т.д.) и сделать код более модульным и тестируемым.

Presentation Layer

Presentation Layer (слой представления) в программировании — это слой архитектуры приложения, который отвечает за отображение данных пользователю и обработку взаимодействий пользователя с приложением. Он играет важную роль в том, как пользователи взаимодействуют с приложением и как данные и команды передаются между пользователем и бизнес-логикой.

  • Bloc - У вас эта папка может называться по другому , все зависит от вашего стейт-менеджера, у меня это блок.

  • Page - здесь хранятся наша верстка.

  • View Model - Здесь находится модель наших экранов

  • Widget - Виджеты которые дают верстке не читабельный вид и требуют вынесения в отдельные файлы (например из-за своей громоздкости).

Core

Папка core представляет собой фундаментальный модуль, содержащий ключевые компоненты, такие как утилиты, маршруты, сеть, службы, валидаторы и т. д. Часть разработчиков кучу папок суют в core, а кто-то разделяет как и features на слои: domain, data, presentation; как это делаю я и большинство разработчиков. Так легче ориентироваться и находить нужные тебе файлы.

Data:

  • Storage - Описание работы с нашим хранилищем.

  • Translation - наша логика перевода.

Domain:

  • Di - di container в котором мы проинициализируем зависимости приложения (классы которые должны быть single ton).

  • Repository - здесь хранится вся логика наших шаблонов применяемых к большому количеству реализаций. Например Dio Interceptor - настраивает все запросы (В моем случае добавляет в хедеры токен, который используется во всех запросах) или ответы. (У меня при ответе сервера 401 ошибки выполняется перезаписывание токена и повторное обращение к серверу)

  • Router - тут находятся наши пути, по которым мы будем перемещаться между экранами.

  • Use_case - тот же шаблон как и repository но применяемый не для самой реализации, а для обработки существующей реализации. Например шаблон ответа от сервера:

sealed class Result<T> {
  bool get isSuccess;

  const factory Result.data(T data) = DataResult;

  const factory Result.error(List<String> errorList) = ErrorResult;
}

class DataResult<T> implements Result<T> {
  final T data;

  const DataResult(this.data);

  @override
  bool get isSuccess => true;
}

class VoidResult<T> implements DataResult<T?> {
  @override
  bool get isSuccess => true;

  @override
  final T? data;

  const VoidResult([this.data]);
}

class ErrorResult<T> implements Result<T> {
  final List<String> errorList;

  const ErrorResult(this.errorList);

  @override
  bool get isSuccess => false;
}

То есть мы обрабатываем уже ответ от существующей реализации.

Presentation:

  • Page - тут находятся наши базовые скрины, такие как BottomNavigation. Которые управляют навигацией всего приложения.

  • Widget - тут все, часто переиспользуемые виджеты.

У чистой архитектуры нет строгих принципов. Это лишь базовые папки и подходы, которые были описаны в этой статье. В каждой компании свои подходы, но познакомившись с той структурой, что описал я, не будет никаких проблем разобраться в каком либо уже существующем проекте. Я вам гарантирую, что в core/domain не будут только те папки, сказаны мною выше. Там может находится логика обработки push уведомлений, логика входа с помощью отпечатка пальца и так далее. Каждый проект несет за собой свои особенности и фичи.

Но если вам скажут создать новый функционал, например реализовать передачу местоположения пользователя в фоном режиме, вы уже знаете что это будет описано по пути core/domain/repository и core/data/repository. В core потому это не является какой-то фичей приложения, у нее нет ui, она работает в любом месте, в любых условиях. В domain потому что у нас выполняется логика - достать место положение. Нам придется создать абстрактный класс:

abstract class Location {
	Location getLocation();
}

Далее в папке core/data/repository- мы уже выполняем его реализацию

class LocationImpl implements Location {
	@override
	Location getLocation(){
		//code
	};
}

Надеюсь, эта статья была для вас полезна, желаю вам удачи 😃

Теги:
Хабы:
Всего голосов 3: ↑2 и ↓1+3
Комментарии3

Публикации

Ближайшие события