Русский
Русский
English
Статистика
Реклама

Androidx

Приглашаем на Mobile Meetup Innopolis

30.06.2020 18:10:31 | Автор: admin


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


Все ли вы знаете об Android Jetpack?



Кирилл Розов, Mobile Lead, Replika / Android Broadcast


Android Jetpack развивается невероятными темпами и уже есть в любом современном Android-приложении. Сейчас сложно представить разработку без этого набора библиотек. Уследить за всеми новинками непросто, поэтому я сделаю обзор последнего API и будущего библиотеки AndroidX.


Подробнее остановлюсь на практиках, которые могли упустить разработчики в последних обновлениях Jetpack. Подсвечу новые возможности, которые позволяют делать код проще и эффективнее.


Приглашаю послушать доклад практикующих Android-разработчиков. Вы узнаете, как сделать интеграцию Dagger 2 c Fragment (без Hilt) и какие API из KTX представляют опасность для использования.


Шаблоны проектирования Server Driven UI



Никита Русин, Platform lead, БюроБюро


Расскажу про подход к проектированию и реализации максимально гибкого клиент-серверного протокола с фокусом на API и кейсах его использования. На проектах часто встречаются задачи создать формы платежей или экраны чеков. Подобные экраны с динамическим контентом и логикой невозможно адекватно реализовать без SD UI.


Тема наиболее актуальна в разработке мобильных и десктопных приложений из-за более сложного и растянутого процесса обновления пользователей. Но доклад будет полезен всем разработчикам обоих концов, как новичкам так и тем, у кого есть серьёзный опыт в проектировании API (для вдохновения или приятного чувства, что в интернете кто-то неправ).


Разберём:


  • Что такое Hypermedia API и Server Driven UI.
  • Каким образом при небольшой подготовке можно клепать 100500 информационных экранов в день, не меняя клиентский код.
  • Способы реализации SD UI на сервере и на клиенте вместе с простыми примерами на Python и Kotlin.
  • Как подготовиться к тому, чтобы на SD UI реализовывать целые пользовательские сценарии/новые разделы без изменений кода клиента.

Будут простые примеры без привязки к конкретной платформе и фреймворку. Так что не важно, каких животных и еду вы предпочитаете.





Вместе со зрителями обсуждать доклады будут модераторы Юрий Новак, ведущий системный аналитик в компании РТ Лабс (Иннополис) и Александр Симоненко, технический директор Технократии (Казань).


Начинаем 2 июля в 17:00


Регистрация напомним о встрече за пару часов


Ссылка на трансляцию


Телеграм-чат митапа


До встречи!

Подробнее..

Разделяй и властвуй Navigation Component в многомодульном проекте

22.01.2021 00:08:40 | Автор: admin

В этой статье вы узнаете, как можно организовать графы отдельных модулей / фич / user story, централизовать их, построить прямую навигацию между ними и присыпать сверху Safe Args плагином.

Вы сейчас в третьей части большого материала про Navigation Component в многомодульном проекте. Если не поняли ни единого слова выше, то призываю сначала ознакомиться с тем:

  • Что за зверь этот Navigation Component.

  • Как работает плагин Safe Args и что он делает.

Ну а если вы уже знакомы с этой библиотекой, то для вас есть приятный бонус в следующей статье подход к организации iOS-like multistack-навигации.

Сначала посмотрим, как выглядит разбиение проекта на модули у нас в компании, в которой я работаю (magora-systems.com):

  1. :app основной модуль и точка входа в приложение. Он должен знать обо всех модулях, участвующих в приложении.

  2. :core-модуль содержит в себе все базовые вещи: базовые классы, модели, entity, DTO, extension-ы и пр.

  3. Утилитарные модули служат для инкапсуляции функционала основных компонентов приложения. Например, работа с сетью, БД или той же навигацией.

  4. Feature-модули заключают в себе работу определенной фичи / user story, будь то флоу или экран.

Что ж, давайте натянем сову на глобус сделаем навигацию с подключенным Safe Args плагином.

Если пользоваться одним единственным графом, то будет так:

Выглядит запутанно и неочевидно. Чтобы не допустить подобного, нужно совершить ряд действий:

  1. Организовать графы для каждого feature-модуля.

  2. Выделить отдельный Top-level граф.

  3. Сделать удобные переходы между графами модулей.

  4. Решить, где хранить Top-level граф.

Теперь подробнее о каждом.

Организовать графы для каждого feature-модуля

Чтобы не было проблем с навигацией между destination-ами внутри одного модуля, сделаем отдельные графы под каждый feature-модуль и там обозначим все конечные точки и связи между ними. При таком подходе навигация фичи полностью инкапсулируется внутри модуля, а Safe Args классы, принадлежащие соответствующему графу, генерируются только тут.

Выделить отдельный Top-level граф

Главный граф это тот, который является стартовой точкой навигации приложения и заключает в себе все необходимые графы, между которыми происходит навигация.

Выглядит не так эффектно, зато эффективно.

Сделать удобные переходы между графами модулей

А для того, чтобы можно было навигироваться между графами различных фич, нужно подвести к каждому global action.

Решить, где хранить Top-level граф

Тут есть несколько вариантов, у каждого есть свои плюсы и минусы:

  1. Базовый модуль (:core)

О нем знает большинство модулей, но не все. Он не знает ни об одном модуле, поэтому не сможет увидеть графы. Стоит заметить, что приложение скомпилится и будет работать несмотря на ошибки Lint-a, при мерже ресурсов все равно всё сливается воедино.

При изменении графа надо будет пересобирать проект, а так как от него зависит много модулей, то их тоже надо будет пересобрать.

  1. Главный модуль (:app)

+ Знает об абсолютно всех модулях.

О нем не знает ни один модуль.

Safe args сгенерирует global action-ы в недоступном для feature-модулей месте, поэтому мы не сможем ходить между графами.

Результаты не то чтобы очень радужные, но у нас есть целый один плюс в пользу :app-модуля, так что оставим Top-level граф в нем и займемся минусами этого подхода.Для этого нам понадобится

Минус: о нем не знает ни один модуль

Решение: сделать отдельный модуль (:navigation), о котором будут знать абсолютно все модули, которые будут хоть как-то взаимодействовать с навигацией.

Добавить в него все id глобальных action-ов. Таким образом generated-файлы поймут, с чем работают, и будут иметь доступ к id каждого глобального перехода.

<item name="actionglobalnavsignin" type="id"/><item name="actionglobalnavsignup" type="id"/><item name="actionglobalnavhome" type="id"/><item name="actionglobalnavuserslist" type="id"/><item name="actionglobalnavuserdetails" type="id"/><item name="actionglobalnavonC11CglobalC12Csettings" type="id"/><item name="actionC13Cto_faq" type="id"/>

Минус: сгенерированные Directions и Args лежат в :app модуле

Safe args сгенерирует global action-ы в недоступном для feature-модулей месте, поэтому мы не сможем ходить между графами.

Решение: перенести и доработать generated-файлы. Тут немного сложнее и придется запачкать руки о билд-скрипты. Generated-классы находятся в build-папке того модуля, где находится граф (сейчас это :app), а использовать его в :navigation-модуле неудобно. Поэтому воспользуемся костылем небольшой хитростью: во время билда дождемся конца работы таски generateSafeArgs, перекинем все созданные файлы в модуль навигации и, так как Args- и Directions-классы используют R файл модуля :app, добавим импорт нашего модуля навигации.

ext {navigationArgsPath = '/build/generated/source/navigation-args'appNavigation = "${project(':app).projectDir.path}$navigationArgsPath"navigationPath = "${project(':navigation').projectDir.path}$navigationArgsPath"navigationPackage = com.example.navigation}tasks.whenTaskAdded { task ->if (task.name.contains('generateSafeArgs')) {task.doLast {fileTree(appNavigation).filter { it.isFile() && it.name.contains("Directions") }.forEach { file ->if (file.exists()) {def lines = file.readLines()lines = lines.plus(2, "import $navigationPackage.R")file.text = lines.join("\n")}}}move(file("$appNavigation"), file("$navigationPath"))}}

В итоге

В результате мы получаем единый флоу работы, при соблюдении которого навигация в приложении декомпозируема, легко масштабируема и работает как часы. Даже если в проекте понадобится переиспользовать какую-нибудь часть графа, то её можно вынести в отдельный модуль, сделать отдельный граф и переиспользовать, как вам заблагорассудится.

Помимо всего прочего мы решили проблему с созданными плагином файлами теперь ребилд в разные флейворы и типы не будет нам докучать, как бы это было с простым добавлением sourceSet-s в модуль навигации.

На этой победе я останавливаться не стал и решил посмотреть, что еще можно с этим сделать. Для этого как никогда кстати подошел проект, в котором заказчик хотел приложение с нижним меню и чтобы каждая вкладка сохраняла свое состояние при уходе с неё. Именно о таком решении финальная часть моей истории про iOS-like multistack-навигацию.

Подробнее..

Навигация в многомодульном приложении на Jetpack без магии и DI

22.04.2021 20:09:52 | Автор: admin

Когда вы начинаете создавать приложение, в котором хотя бы несколько экранов, всегда встает вопрос - как лучше реализовать навигацию. Вопрос становится интереснее и сложнее, когда вы собираетесь делать многомодульное приложение. Примерно полтора года назад я рассказывал как можно реализовать навигацию c помощью Jetpack в многомодульном проекте. И вот спустя время, я наткнулся на свою реализацию и понял, что можно на том же Jetpack летать по модулям проще: без магии и DI.


Архитектура проекта

Чтобы покрыть основные кейсы я покажу как реализовать навигацию на многомодульном проекте такой структуры:

Типичная архитектура Android проекта: feature-модули c реализацией экранов зависят от shared-модулей с общей логикой. И app модуль, который зависит от feature и shared.

Сейчас довольно популярен подход Single Activity, поэтому в моем примере будет всего одна Activity с глобальным хостом, в котором будут переключаться фрагменты

Подготовка

От модуля shared:navigation зависят почти все модули проекта не просто так. В этом модуле реализована функция расширения фрагмента для реализации переходов.

fun Fragment.navigate(actionId: Int, hostId: Int? = null, data: Serializable? = null) {val navController = if (hostId == null) {findNavController()} else {Navigation.findNavController(requireActivity(), hostId)}val bundle = Bundle().apply { putSerializable("navigation data", data) }navController.navigate(actionId, bundle)}

У функции есть параметры:

  • actionId - id действия графа навигации

  • hostId - id хоста графа навигации. Если не будет передан, то будет использован текущий хост

  • data - объект с данными типа Serializable

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

val Fragment.navigationData: Serializable?get() = arguments?.getSerializable("navigation data")

Также в этом модуле надо описать id хостов навигации, чтобы к ним был доступ из feature модулей. Для этого в директории ресурсов надо создать файл res/value/ids.xml

<?xml version="1.0" encoding="utf-8"?><resources><item name="host_global" type="id"/><item name="host_main" type="id"/></resources>

Отлично! Подготовка завершена, можно приступать к самой реализации навигации.

Простые переходы в feature-модулях

Сэмулируем типичное поведение экрана splash. Обычно с этого экрана идет переход либо к онбордингу, либо к главному экрану приложения, либо к экрану авторизации. Реализуем нечто похожее: пусть фрагмент фичи splash будет уметь переходить на экран онбординга и на главный экран по нажатию кнопки.

Для начала создади id для этих действий: запишем их в res/value/ids.xml модуля splash

<?xml version="1.0" encoding="utf-8"?><resources><item name="action_splashFragment_to_mainFragment" type="id"/><item name="action_splashFragment_to_onboardingFragment" type="id"/></resources>

Id для действий переходов я рекомендую создавать именно в модулях фич, которые будут использовать эти действия, а не в модуле shared:navigation. Это позволяет модулю знать только о необходимых действиях.

Теперь можно использовать созданные id для выполнения переходов.

import com.example.smmn.shared.navigation.navigateclass SplashFragment : Fragment(R.layout.fragment_splash) {    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        buttonToOnboarding.setOnClickListener {            navigate(R.id.action_splashFragment_to_onboardingFragment)        }        buttonToMain.setOnClickListener {            navigate(R.id.action_splashFragment_to_mainFragment)        }    }}

Обратите внимание, что для выполнения перехода используется функция расширения из модуля shared:navigation.

Но чтобы этот переход заработал надо настроить глобальный хост и реализовать глобальную навигацию.

Глобальный хост

В нашей архитектуре всего одна Activity. Она будет содержать глобальный хост для фрагментов. Для этого нам ничего не потребуется реализовывать в самом коде Activity.

class MainActivity : AppCompatActivity(R.layout.activity_main)

Хост добавить надо в ее разметке activity_main.xml

<?xml version="1.0" encoding="utf-8"?><androidx.fragment.app.FragmentContainerView xmlns:android="http://personeltest.ru/away/schemas.android.com/apk/res/android"    xmlns:app="http://personeltest.ru/away/schemas.android.com/apk/res-auto"    xmlns:tools="http://personeltest.ru/away/schemas.android.com/tools"    android:id="@id/host_global"    android:name="androidx.navigation.fragment.NavHostFragment"    android:layout_width="match_parent"    android:layout_height="match_parent"    app:defaultNavHost="true"    app:navGraph="@navigation/navigation_global"    tools:ignore="FragmentTagUsage" />

Глобальная навигация

Это навигация, которая происходит в глобальном хосте. Для ее реализации надо реализовать в модуле app граф навигации res/navigation/navigation_global.xml

<?xml version="1.0" encoding="utf-8"?><navigation xmlns:android="http://personeltest.ru/away/schemas.android.com/apk/res/android"    xmlns:app="http://personeltest.ru/away/schemas.android.com/apk/res-auto"    android:id="@+id/navigation_global"    app:startDestination="@id/splashFragment">    <fragment        android:id="@+id/splashFragment"        android:name="com.example.smmn.feature.splash.SplashFragment"        android:label="SplashFragment">        <action            android:id="@id/action_splashFragment_to_mainFragment"            app:destination="@id/mainFragment"            app:popUpTo="@id/navigation_global" />        <action            android:id="@id/action_splashFragment_to_onboardingFragment"            app:destination="@id/onboardingFragment"            app:popUpTo="@id/navigation_global" />    </fragment>    <fragment        android:id="@+id/mainFragment"        android:name="com.example.smmn.feature.main.MainFragment"        android:label="MainFragment" >        <action            android:id="@id/action_mainFragment_to_splashFragment"            app:popUpTo="@id/navigation_global"            app:destination="@id/splashFragment" />    </fragment>    <fragment        android:id="@+id/onboardingFragment"        android:name="com.example.smmn.feature.onboarding.OnboardingFragment"        android:label="OnboardingFragment">        <action            android:id="@id/action_onboardingFragment_to_mainFragment"            app:destination="@id/mainFragment"            app:popUpTo="@id/navigation_global" />    </fragment></navigation>

Обратите внимание, что у каждого фрагмента есть набор action (действий) с помощью которых происходит переход между фрагментами. В действии указывается на какой фрагмент будет выполнен переход и как обрабатывать переход назад, например, при нажатии кнопки "Back".

И очень важно отметить, что id действий прописаны без знака +, то есть мы не создаем id в этом графе, а используем id, прописанные в feature модуле.

Прописанные id в модуле splash

<item name="action_splashFragment_to_mainFragment" type="id"/><item name="action_splashFragment_to_onboardingFragment" type="id"/>

Использование их в действиях глобального графа

        <action            android:id="@id/action_splashFragment_to_mainFragment"            app:destination="@id/mainFragment"            app:popUpTo="@id/navigation_global" />        <action            android:id="@id/action_splashFragment_to_onboardingFragment"            app:destination="@id/onboardingFragment"            app:popUpTo="@id/navigation_global" />

Вложенный хост

В Jetpack навигации есть возможность использовать вложенный хост. Это очень полезно, когда мы хотим сделать меню типа BottomNavigation и использовать для этого меню отдельный граф навигации.

В нашем примере во вложенном хосте будут фичи профиля и настроек.

Благодаря библиотеке navigation-ui, реализовать вложенную навигацию довольно просто.

В модуле main создадим меню для BottomNavigation в res/menu/menu_main.xml

<?xml version="1.0" encoding="utf-8"?><menu xmlns:android="http://personeltest.ru/away/schemas.android.com/apk/res/android">    <item        android:id="@+id/profileFragment"        android:icon="@drawable/ic_baseline_account_circle_24"        android:title="@string/main_menu_title_profile" />    <item        android:id="@+id/settingsFragment"        android:icon="@drawable/ic_baseline_settings_24"        android:title="@string/main_menu_title_settings" /></menu>

Создадим граф навигации в res/navigation/navigation_main.xml

<?xml version="1.0" encoding="utf-8"?><navigation xmlns:android="http://personeltest.ru/away/schemas.android.com/apk/res/android"    xmlns:app="http://personeltest.ru/away/schemas.android.com/apk/res-auto"    android:id="@+id/navigation_main"    app:startDestination="@id/profileFragment">    <fragment        android:id="@+id/profileFragment"        android:name="com.example.smmn.feature.profile.ProfileFragment"        android:label="ProfileFragment">        <action            android:id="@id/action_profileFragment_to_infoFragment"            app:destination="@id/infoFragment" />    </fragment>    <fragment        android:id="@+id/settingsFragment"        android:name="com.example.smmn.feature.settings.SettingsFragment"        android:label="SettingsFragment" />    <fragment        android:id="@+id/infoFragment"        android:name="com.example.smmn.feature.info.InfoFragment"        android:label="InfoFragment" /></navigation>

Здесь важно указать у фрагментов те же id что указаны в файле меню res/menu/menu_main.xml. И не забывать, что id действий брать из модулей фич.

Осталось добавить хост и меню в разметку фрагмента res/layout/fragment_main.xml

<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://personeltest.ru/away/schemas.android.com/apk/res/android"    xmlns:app="http://personeltest.ru/away/schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent">    <fragment        android:id="@id/host_main"        android:name="androidx.navigation.fragment.NavHostFragment"        android:layout_width="match_parent"        android:layout_height="0dp"        app:defaultNavHost="true"        app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="parent"        app:navGraph="@navigation/navigation_main" />    <com.google.android.material.bottomnavigation.BottomNavigationView        android:id="@+id/bottomNavigationView"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:background="@android:color/white"        app:elevation="8dp"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:menu="@menu/menu_main" /></androidx.constraintlayout.widget.ConstraintLayout>

И в самом фрагменте настроить bottomNavigationView

class MainFragment : Fragment(R.layout.fragment_main) {    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        NavigationUI.setupWithNavController(            bottomNavigationView,            Navigation.findNavController(requireActivity(), R.id.host_main)        )    }}

Переходы между фрагментами из разных хостов

Довольно частый случай, когда надо перейти с экрана, который находится внутри вложенного хоста, на экран глобального хоста. Например, у нас есть главный экран c хостом для экранов главных фич: настроек и профиля.

И на экране настроек, который находится внутри хоста главного экрана (не глобальный хост, а глубже) надо выполнить переход на экран сплэша, который находится в глобальном хосте. Например это может понадобиться, если надо разлогинить текущего пользователя.

В этом случае также воспользуемся функцией расширения фрагмента, но укажем id глобального хоста. Мы имеем к нему доступ из фичи, так как он прописан в модуле shared:navigation.

class SettingsFragment : Fragment(R.layout.fragment_settings) {    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        buttonToSplash.setOnClickListener {            navigate(R.id.action_mainFragment_to_splashFragment, R.id.host_global)        }    }}

Id действия по аналогии с предыдущим переходом прописан в самом модуле фичи res/values/ids.xml

<?xml version="1.0" encoding="utf-8"?><resources><item name="action_mainFragment_to_splashFragment" type="id"/></resources>

Переходы между фрагментами с передачей и получением данных

Чтобы выполнить переход с передачей данных необходимо, чтобы данные можно было положить в bundle. Это могуг быть какие-то примитивные типы или объекты Serializable классов.

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

Чтобы передать объект Serializable класса надо чтобы модуль фичи, с которой происходит переход, и модуль фичи, на которую происходит переход, имели доступ к модулю с таким классом. В нашем случае создадим модуль shared:model где будет лежать Serializable класс Info.

data class Info(    val name: String,    val surname: String) : Serializable

Переход будет происходить с экрана profile на экран info. Создадим объект Info и передадим его в функцию расширения фрагмента.

class ProfileFragment : Fragment(R.layout.fragment_profile) {    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        buttonToInfo.setOnClickListener {            navigate(R.id.action_profileFragment_to_infoFragment, data = Info("name", "surname"))        }    }}

И получим данные используя другую функцию расширения фрагмента, созданную ранее.

class InfoFragment : Fragment(R.layout.fragment_info) {    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        val info = navigationData as? Info ?: return        textView.text = info.toString()    }}

Так это будет выглядеть в приложении

Заметьте, что мы не указывали в каком хосте выполнить переход, и переход произошел в текущем хосте.

Заключение

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

Оставляю ссылку на код примера приложения.

Буду рад обратной связи!

Подробнее..

Из песочницы Hilt еще один DI?

11.08.2020 20:13:10 | Автор: admin

Встречайте Hilt Dependency Injection (DI) в JetPack, но это не правда, так как Hilt это просто обертка для Dagger2. Для небольших проектов сможет встать более удобным инструментом и хорошо интегрируется с остальными продуктами в JetPack.


Не буду описывать как добавить в проект, все хорошо описано в статье


Зачем?


Dagger2 довольно сложный DI, можно легко запутаться, что и куда поместил. Придумывать бесконечное количество компонентов и увеличивается количество абстракций. И чем больше проект, тем больше увеличивается путаница.


И если хочется использовать Dagger2, но с минимальными усилиями, то для этого как раз и был придуман Hilt.


Что упростили для нас:


  • Готовые компоненты (из названий понятно к чему относятся)
    • ApplicationComponent
    • ActivityRetainedComponent
    • ActivityComponent
    • FragmentComponent
    • ViewComponent
    • ViewWithFragmentComponent
    • ServiceComponent
  • В модуле указываешь в какой компонент добавить
  • Через @AndroidEntryPoint Hilt компилятор генерирует весь bolierplate для создания компонента и хранения (например, ActivityRetainedComponent сохранит сущность после поворота экрана, ActivityComponent пересоздаст заново).

Такой код выглядит довольно элегантно (весь boilerplate за нас сгенерируется)


@AndroidEntryPointclass ExampleActivity : AppCompatActivity() {     @Inject lateinit var testService: TestService}

Особенности


Application обязателен


Необходимо объявить Application и пометить @HiltAndroidApp, без него Hilt не заработает.


@HiltAndroidAppclass App : Application() { }

Иерархическая зависимость


Если хотите использовать Hilt в фрагментах, то Activity которая содержит эти фрагменты обязательно помечать аннотацией @AndroidEntryPoint


Если View пометить @WithFragmentBindings то Fragment должен быть с аннотацией @AndroidEntryPoint, а без этой аннотации зависит от того куда инжектимся Activity или Fragment


Объявление модулей


Все как в Dagger2, но нет необходимости добавлять модуль в компонент, а достаточно использовать аннотацию @InstallIn. Это и понятно, так как компоненты не доступны для редактирования.


@InstallIn(ApplicationComponent::class)@Moduleclass NetworkModule {    @Singleton    @Provides    fun provideHttpService(): HttpService {        return object : HttpService {            init {                Log.e("Tester", "HttpService initialized")            }            override fun request() {                Log.e("Tester", "HttpService::request")            }        }    }}

При добавления Hilt, все модули должны быть с @InstallIn, либо компилятор ругнется, что аннотация отсутствует.


Кастомные Component и Subcomponent


Создать их конечно можно, так как там Dagger2, но теряется тогда весь смысл Hilt и все придется писать вручную. Вот что предлагается в документации:


DaggerLoginComponent.builder()        .context(this)        .appDependencies(          EntryPointsAccessors.fromApplication(            applicationContext,            LoginModuleDependencies::class.java          )        )        .build()        .inject(this)

Поддержка многомодульности


Все это есть, но только когда используем только готовые компоненты. Но если добавить для каждого модуля свои компоненты (как советуют тут), то лучше использовать Dagger2.


Ограничения для @AndroidEntryPoint


  • Поддерживаются Activity наследуемые от ComponentActivity и AppCompatActivity
  • Поддерживаются Fragment наследуемые от androidx.Fragment
  • Не поддерживаются Retain фрагменты

Что внутри


Hilt работает следующим образом:


  • Генерируются Dagger Component-ы
  • Генерируются базовые классы для Application, Activity, Fragment, View и т.д, которые помечены аннотацией @AndroidEntryPoint
  • Dagger компилятор генерирует статический коды

Как устроено хранение ActivityRetainedComponent


Не стали усложнять и просто поместили компонент в ViewModel из arch библиотеки:


this.viewModelProvider =        new ViewModelProvider(            activity,            new ViewModelProvider.Factory() {              @NonNull              @Override              @SuppressWarnings("unchecked")              public <T extends ViewModel> T create(@NonNull Class<T> aClass) {                ActivityRetainedComponent component =                    ((GeneratedComponentManager<LifecycleComponentBuilderEntryPoint>)                            activity.getApplication())                        .generatedComponent()                        .retainedComponentBuilder()                        .build();                return (T) new ActivityRetainedComponentViewModel(component);              }            });

Итог


Плюсы:


  • Более простое использование чем Dagger2
  • Добавление модулей через аннотацию выглядит довольно удобно (в несколько уже не поместишь)
  • Код чище и много boilerpate спрятано.
  • Все плюсы от Dagger2 (генерация статического кода, валидация зависимостей и т.д.)
  • Удобен для небольших проектов

Минусы:


  • Тяжело избавится, не только захочется убрать или заменить, но и просто перейти на Dagger2
  • Тяжело добавить кастомные компоненты, что ограничивает использование с крупных проектах
  • Наследует минусы Dagger2 и еще больше увеличивает время сборки
  • Иерархическая зависимость, например, нельзя использовать в Fragment без Activity c @AndroidEntryPoint

Полезные ссылки:


Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru