Внедрение зависимостей во Flutter во время компиляции
Эй, ребята! Я вернулся с еще одной совершенно новой статьей о Flutter. На этот раз я расскажу об очень интересном и важном шаблоне проектирования в мире разработки программного обеспечения, а именно о «внедрении зависимостей». Это поможет вам создавать слабосвязанные классы, что в конечном итоге приведет к поддерживаемой и тестируемой кодовой базе. Так что возьмите чашку кофе, найдите тихое место и узнайте, как я использую DI в проекте Flutter.
Содержание
- Что такое внедрение зависимостей?
- Добавление inject.dart package как подмодуль к проекту
- Понимание аннотаций(@provide, @module и т. д.)
- Написание модулей и класса Injector
- Разрешение зависимостей во время компиляции 😍
- Соберите и запустите приложение 🏃
В этой статье я покажу вам, как провести рефакторинг проекта Flutter, в котором не используется какая-либо структура DI. Таким образом, чтобы в полной мере воспользоваться содержанием, я ожидаю, что читатель построил как минимум 2 или 3 приложения использовать Flutter и иметь некоторое базовое представление о шаблоне BLoC. Если вы новичок в шаблоне BLoC, то эти два ( ЧАСТЬ 1 а также Часть 2 ) статьи — лучшее место для начала, так как я буду рефакторить тот же проект, чтобы инжекторный блок а также хранилище объекты в дерево виджетов.
Что такое внедрение зависимостей?
Позвольте мне объяснить вам, взяв пример из реальной жизни. Вы знаете, как производятся мобильные телефоны? Позвольте мне объяснить в основных терминах. Для сборки мобильного телефона вам в основном нужны следующие компоненты:
- Печатная плата, содержащая мозг телефона.
- Антенна.
- Жидкокристаллический дисплей (LCD)
- Микрофон.
- Спикер.
- Батарея.
Теперь все эти компоненты не производятся на одном заводе. Эти компоненты производятся в разных странах (при условии). Затем все эти отдельные компоненты импортируются и собираются вместе на одном заводе. Это » Внедрение зависимости ». Сборочное производство было не зная процесса сборки различных компонентов. Единственной задачей фабрики была сборка этих компонентов для создания телефона. Таким образом, фабрике не нужно беспокоиться о процессе сборки этих компонентов, и она может сосредоточиться только на своей работе, т.е. «собирать необходимые компоненты для сборки телефона». Я думаю, что этого небольшого примера достаточно для начала, но для любопытных, которые хотят больше узнать о DI. Проверьте это удивительное статья .
Настраивать
Давайте импортируем Мои фильмы проект с GitHub и запустите его один раз, чтобы проверить, все ли работает нормально.
Итак, если вы посмотрите на проект. У меня есть структура пакета ниже. Я ожидаю, что даже у вас есть то же самое. Просто убедитесь, что мы на одной странице:
Проблема
Позвольте мне показать вам один пример того, в чем проблема с текущей структурой кода. Если вы откроете movie_api_provider.dart
файл и увидите вторую строку, вы обнаружите, что я создаю новый Client()
предмет внутри MovieApiProvider
учебный класс. Но согласно объяснению, которое я дал вам в предыдущем разделе о DI. Этого не должно быть. MovieApiProvider
не должны нести ответственность за создание Client
объект, но вместо этого он должен получить Client
объект, предоставленный кем-то.
Это только один пример. Если вы проверите другие классы, такие как repository.dart
а также movies_bloc.dart
вы найдете тот же случай. Эти классы не должны нести ответственность за создание объектов классов, от которых они зависят. Вместо этого они(repository.dart
или же movies_bloc.dart
) должны быть обеспечены требуемыми объектами из какого-либо другого класса или фабрики.
Еще одна замечательная особенность этого шаблона проектирования (DI) заключается в том, что нам даже не нужно BlocProviders (InheritedWidget
) больше, чтобы предоставить нам блок или любой другой тип объекта. InheritedWidget не будет разрешать зависимости во время сборки, но выдаст исключение во время выполнения, если не удалось правильно подключить зависимости.
Итак, как мы можем снять с этих классов ответственность за создание объектов? Как мы можем создать фабрику, которая будет нести ответственность за создание и предоставление зависимых объектов этим классам? Давай выясним. 😃
inject.dart нам в помощь
мы будем использовать inject.dart для решения проблем, о которых я говорил выше. Это с открытым исходным кодом время компиляции пакет внедрения зависимостей для Dart и Flutter из внутреннего репозитория внутри Google. Этот пакет поможет нам построить фабрику, где мы сможем создавать все зависимые объекты отдельно и внедрять их туда, где они требуются. Самое приятное в этом пакете то, что он разрешит все зависимости во время сборки. Не будет никаких исключений во время выполнения, связанных с подключением зависимостей к провайдерам. Другими словами, перед запуском приложения убедитесь, что все объекты созданы или все зависимости разрешены. Удивительно, не так ли?
Этот фреймворк не «утекает», не имеет побочных эффектов и не требует зависимостей для его использования. Этот фреймворк использует только ядро dart:*
библиотеки, поэтому он не зависит от платформы (Flutter или AngularDart).
Добавление inject.dart Добавление этого пакета будет немного отличаться от того, как мы обычно добавляем плагины в наш проект. Создайте папку и назовите ее injection
(можно назвать что угодно) внутри MyMovies
каталог. Откройте терминал, перейдите в папку, которую вы только что создали, и выполните следующую команду:
git submodule add
Теперь вы увидите следующую структуру пакета:
Не обращайте внимания на эти ошибки. Это не будет препятствием для нашего продвижения.
Добавьте следующие зависимости в свой pubspec.yaml
:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
rxdart: ^0.18.0
http: ^0.12.0+1
inject:
path: ./injection/inject.dart/package/inject
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^1.0.0
inject_generator:
path: ./injection/inject.dart/package/inject_generator
build_runner
package предоставляет конкретный способ создания файлов с использованием кода Dart. В конце концов, когда мы будем собирать проект, вы будете использовать build_runner
.
Создайте новый пакет под src
и назови это di
. Теперь добавьте два файла dart в пакет di и назовите их как bloc_injector.dart
а также bloc_module.dart
.
Написание модулей и класса Injector
Приступаем к работе над bloc_module.dart
сначала файл. Скопируйте и вставьте следующий код в файл:
Если вы использовали Кинжал раньше вы поймете, что происходит. Но для других позвольте мне дать вам некоторые пояснения по поводу используемых здесь аннотаций. Вы увидите некоторые ошибки, но не волнуйтесь, мы скоро их исправим.
Понимание аннотаций
@module
: Эта аннотация сделает BlocModule
класс, доступный для внесения вклада в граф зависимостей, который inject.dart
будет генерировать. Он будет содержать все зависимости, требуемые другими классами.
@provide
: все методы, аннотированные этим, доступны для внедрения зависимостей. Проверка приведенного выше кода прояснит ситуацию.
@singleton
: Как следует из названия, он создаст и предоставит один экземпляр объекта.
Теперь откройте bloc_injector.dart
и добавьте следующий код в файл:
Вы должны видеть некоторые ошибки. Но не беспокойтесь, bloc_injector.inject.dart
файл будет создан во время сборки, и все будет решено. Как только файл будет сгенерирован, мы изучим create()
также метод. Еще одна вещь, которую следует отметить здесь, это App get app;
это корневой виджет нашего проекта, и мы объявляем его здесь, чтобы указать место назначения, куда будут внедрены зависимости.
@Injector
: Помните, ранее мы говорили о фабрике, которая будет собирать зависимые объекты. Это то, что будет делать эта аннотация. Он все склеит и сделает все объекты доступными для инъекций. Это принимает все зависимые модули в качестве аргумента, потому что именно так инжектор узнает, какие все объекты должны быть разрешены и предоставлены.
Открытым main.dart
файл и вставьте в него следующий код:
Я объясню вам, что это container
объект после того, как мы сгенерируем bloc_injector.inject.dart
файл.
Открытым app.dart
файл и вставьте в него следующий код:
Вы увидите некоторые ошибки, но мы скоро их исправим. В приведенном выше фрагменте кода оба MoviesBloc
а также MoviesDetailBloc
объекты предоставляются посредством внедрения конструктора, а затем передаются соответствующим классам, т.е. MovieList
а также MovieDetail
экран. Здесь нет создания объекта. Это чертовски удивительно! 😲 👏
Создайте новый файл под blocs
пакет и назовите его как bloc_base.dart
. Вставьте в него приведенный ниже код:
Открытым movie_detail_bloc.dart
файл и вставьте в него следующий код:
Как вы можете видеть в приведенном выше коде, я не создаю Repository
объект внутри MovieDetailBloc
учебный класс. Вместо этого я ввел объект в качестве аргумента конструктора. 😲 😲
Открытым movies_bloc.dart
файл и вставьте в него следующий код:
То же самое! я не создаю Repository
предмет внутри MovieBloc
учебный класс. Вместо этого он будет предоставлен откуда-то извне. Ух ты!! 😲
Открытым movie_api_provider.dart
файл и вставьте ниже код:
Как видите, я не создаю Client
предмет внутри MovieApiProvider
учебный класс. @provide
используется, чтобы помочь платформе DI выяснить, где требуются все зависимости, и соответствующим образом сгенерировать код.
Открытым repository.dart
файл и вставьте в него следующий код:
Лучшая часть, movieApiProvider
объект будет создан только один раз, потому что мы аннотировали MovieApiProvider
как singleton
при создании в BlocModule
.
Открытым movie_detail.dart
файл и вставьте ниже код:
Здесь MovieDetailBloc
вводится как параметр конструктора. И есть небольшое изменение в том, как bloc
объект используется в initState()
. Не стесняйтесь исследовать init()
метод. я не использую MovieDetailBlocProvider
(InhertiedWidget
) class больше, чтобы получить доступ к bloc
объект. мне даже не нужен context
больше. 😉
Открытым movie_list.dart
файл и вставьте в него следующий код:
Здесь такие же удивительные вещи. MoviesBloc
объект вводится сюда через конструктор.
Открытым item_model.dart
файл и замените этот код:
Я только что сделал небольшое изменение. Я сделал класс Result общедоступным, чтобы мы могли передавать аргументы типа Result
по названным маршрутам.
Мы закончили с изменением структуры кода. Теперь сосредоточимся на том, как скомпилировать проект. Это действительно важный шаг. Создайте новый файл в каталоге проекта и назовите его как inject_generator_build.yaml
. Вот где это должно быть размещено:
Скопируйте и вставьте в него приведенный ниже код:
Мы создали этот файл, потому что по умолчанию все сгенерированные файлы будут помещаться в папку кеша, и Flutter не сможет получить доступ к файлам оттуда (но над решением этой проблемы ведется работа). Так что будем менять build_to: cache
к build_to: source
.
Разрешение зависимостей во время компиляции 😍
Пришло время сгенерировать код 🔥. Откройте терминал и перейдите в папку проекта. Запустите команду ниже:
flutter packages pub run build_runner build
Соберите и запустите приложение 🏃
Если build_runner
удалось разрешить все зависимости, вы увидите следующий вывод:
Поздравляю! Если вы добрались до сюда. Вы успешно создали граф зависимостей. Ты гений. 👏 👏 👏 (Если у вас возникнут какие-либо проблемы, дайте мне знать о вашей проблеме, оставив комментарий или связавшись со мной через LinkedIn или Twitter).
Очистка
После успешной генерации кода. Вы можете увидеть много сгенерированных файлов, добавленных в ваш проект:
Не паникуйте! Это нормально. Вы можете удалить все *.inject.dart
а также *.summary
файлы кроме bloc_injector.inject.dart
. Вы даже можете удалить movie_detail_bloc_provider.dart
файл. Чтобы автоматизировать эту задачу. Перейдите в lib и другие подкаталоги src
и запустите команду ниже (это удалит только пустые файлы):
find . -size 0 -delete
Изучение сгенерированного графа
Вот Это Да! Это выглядит так красиво ❤️. Как вы можете видеть в сгенерированном коде, оба MoviesBloc
а также MovieDetailBloc
объекты передаются как параметры конструктора в App
Виджет. Если вы внимательно просмотрите этот файл, вы поймете цель BlocModule
а также bloc_injector.dart
файл.
Отправка вашего проекта на git
Прежде чем отправлять этот вновь созданный проект в свою учетную запись git, добавьте следующую строку в свой .gitignore
файл:
*.inject.summary
Это действительно большое и мощное изменение, которое мы внесли в наш проект. Теперь вы можете легко внедрить объекты любого типа в дерево виджетов извне. Таким образом, вы сохраните свою кодовую базу в сопровождении и тестировании. В моей следующей статье я покажу вам, как протестировать ваш код с помощью внедрения зависимостей.
Я создал отдельный ответвляться для этой реализации. Это поможет вам сравнить обе реализации (без DI и с DI) и в конечном итоге выбрать то, что соответствует вашим потребностям.
Надеюсь, вам понравилась статья. Покажите свою любовь к моей работе, поставив лайк этой статье. Не стесняйтесь связаться со мной в Твиттер или же LinkedIn за любую помощь или запрос.