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

Grudle plugin

Перевод Koin библиотека для внедрения зависимостей, написанная на чистом Kotlin

26.11.2020 16:14:32 | Автор: admin

Как управлять внедрением зависимостей с помощью механизма временной области (scope)

Для будущих студентов курса "Android Developer. Professional" подготовили перевод полезной статьи.

Также приглашаем принять участие в открытом вебинаре на тему "Пишем Gradle plugin"


О чем эта статья

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

Введение

Разработчики ОС Android не рекомендуют использовать внедрение зависимостей (Dependency Injection, DI (англ.)), если в вашем приложении три экрана или меньше. Но если их больше, лучше применить DI.

Популярный способ реализации DI в Android-приложениях основан на фреймворке Dagger. Но он требует глубокого изучения. Одна из лучших альтернатив этому фреймворку Koin, библиотека, написанная на чистом Kotlin.

Если вы уже пользовались Dagger или любой другой библиотекой для DI, то, скорее всего, знаете, насколько важен в этом процессе механизм временной области (scope). Он позволяет определять, в каких случаях нужно получать один и тот же зависимый объект, а в каких новый. Он также помогает освобождать невостребованные ресурсы и память.

Области в Koin

Концепция области в Koin аналогична таковой в Android. Она позволяет, например, ограничить область живучести модели представления (ViewModel) до определенной активности и использовать эту модель во фрагментах, которыми наполняется активность.

Как правило, в Koin три вида временных областей.

  • single (одиночный объект)создается объект, который сохраняется в течение всего периода существования контейнера (аналогично синглтону);

  • factory (фабрика объектов) каждый раз создается новый объект, без сохранения в контейнере (совместное использование невозможно);

  • scoped (объект в области) создается объект, который сохраняется в рамках периода существования связанной временной области.

Одиночный объект(single). Фабрика объектов(factory)Одиночный объект(single). Фабрика объектов(factory)

Область вида single при каждом запросе возвращает один и тот же экземпляр, а factory каждый раз возвращает новый экземпляр.

Настраиваемая область

Стандартные области single и factory в Koin живут в течение жизненного цикла модулей Koin. Однако в реальных сценариях использования требования к внедрению зависимостей будут отличаться.

Зависимости обычно нужны только на определенный период времени. Например, репозиторий OnBoardRepository в Android-приложении требуется только при регистрации пользователя. Как только пользователь авторизуется, удержание этого репозитория в памяти станет лишней тратой ресурсов.

Чтобы добиться нужного поведения в Koin, можно воспользоваться API для работы с временными областями. В модуле Koin можно создать область со строковым квалификатором и объявить зависимости внутри нее с помощью уникальных квалификаторов. Давайте сделаем это шаг за шагом.

Шаг 1

Сначала создадим модуль, объявим пустую область и присвоим ей имя. В данном случае мы дали области имя CustomScope. Вы можете назвать ее в соответствии со своими требованиями. Вот как это выглядит:

creating custom koin scope

Шаг 2

Следующим шагом объявим необходимые зависимости с использованием областей single и factory в соответствии с требованиями проекта. Ключевой момент заключается в присвоении областям уникальных квалификаторов. Вот так:

dependencies inside custom scopes

Шаг 3

Мы закончили настройку в модуле Koin. На этом шаге нам нужно создать область из того компонента, из которого мы импортируем нужные зависимости. Обычно области создаются из Android-компонентов, например Activity,Fragment и т.п.

Чтобы создать область, сначала нам нужно получить существующий экземпляр компонента Koin, а затем вызвать функцию createScope, передав ей идентификатор и имя области.

val stringQualifiedScope = getKoin().createScope(    "ScopeNameID", named("CustomeScope"))

Получив CustomScope как значение параметра имени, Koin будет искать область, которую мы объявили под этим именем в модулях Koin. ScopeNameID это идентификатор, который мы применяем, чтобы отличать одну область от другой. Он используется на внутреннем уровне в качестве ключа для поиска этой области.

Если вы обращаетесь к областям или создаете их из нескольких Android-компонентов, то вместо функции createScope рекомендуется использовать функцию getOrCreateScope. Из названий этих функций очевидно, что они делают.

Шаг 4

Наконец, создаем экземпляр зависимости, которую хотим использовать. Мы сделали это с помощью созданной нами области. Вот что получилось.

val sampleClass = stringQualifiedScope.get<SampleClass>(    qualifier = named("scopedName"))

scopedName и factoryName это квалификаторы, которые мы объявили внутри модуля Koin на шаге2.

Шаг 5

Чтобы избавиться от зависимостей, созданных посредством stringQualifiedScope, вчастности sampleclass, необходимо вызвать функцию close. Например, если вы хотите избавиться от созданных в этой области зависимостей при уничтожении активности, то нужно вызвать функцию close в рамках соответствующего метода onDestroy. Вот так:

override fun onDestroy() {    super.onDestroy()    stringQualifiedScope.close()}

Koin-Android

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

Для этого необходимо импортировать библиотеки Koin-Android. Добавьте следующие строки в узел dependencies файла build.gradle уровня приложения:

// Koin for Androidimplementation "org.koin:koin-android:$koin_version"// Koin Android Scope featuresimplementation "org.koin:koin-android-scope:$koin_version"

Модули Koin-Android

Теперь с целью сократить шаблонный код мы хотим, например, автоматически закрывать область в рамках метода onDestroy компонента Android. Это можно сделать путем привязки Koin к импорту зависимостей посредством lifecyclescope.

Для начала необходимо создать в модуле Koin область для зависимостей с компонентами Android. Как это сделать:

val androidModule = module {    scope<SampleActivity> {        scoped { SampleClass() }    }  }

scoping dependency with android activity

Затем нужно выполнить внедрение зависимости в активности при помощи lifecyclescope:

val sampleClass : SampleClass by lifecycleScope.inject()

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

@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)    fun onDestroy() {    if (event == Lifecycle.Event.ON_DESTROY) {               scope.close()        }    }}

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

Дополнительные материалы

На этом все. Надеюсь, вы узнали что-то полезное для себя. Спасибо за внимание!


Подробнее о курсе "Android Developer. Professional". Записаться на открытый урок "Пишем Gradle plugin" можно здесь.

Подробнее..

Перевод Рисование собственных представлений (View) в Android

01.12.2020 18:05:13 | Автор: admin

Получите полный контроль над представлением и оптимизируйте его производительность

В преддверии старта курса "Android Developer. Professional" приглашаем всех желающих принять участие в открытом вебинаре на тему "Пишем gradle plugin".

А пока делимся переводом полезного материала.


Введение

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

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

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

Зачем создавать собственные представления?

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

  1. Производительность: в вашем макете много представлений и вы хотите оптимизировать их, нарисовав одно, более легкое собственное представление.

  2. Имеется сложная иерархия представлений, которую трудно использовать и поддерживать.

  3. Необходимо создать специализированное представление, требующее рисования вручную.

Общий подход

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

  1. Создать класс, расширяющий базовый класс или подкласс представления.

  2. Реализовать конструкторы, использующие атрибуты из XML-файла.

  3. Переопределить некоторые методы родительского класса (onDraw(), onMeasure() и т.д.) в соответствии с нашими требованиями.

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

Пример

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

Шаг 1. Создадим класс с именем CircularTextView.

Шаг 2. Расширим класс виджета TextView. Здесь под TextView в IDE выдается ошибка, в которой сообщается, что у этого типа есть конструктор и он должен быть инициализирован.

Шаг 3. Добавим конструкторы в класс.

Это можно сделать двумя способами.

Первый способ добавления конструкторов в класс показан ниже.

Другой способ заключается в добавлении аннотации @JvmOverloads к вызову конструктора, как показано ниже.

Часто нас сбивает с толку то, что у представления есть несколько разных типов конструкторов.

View(Context context)

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

View(Context context, @Nullable AttributeSet attrs)

Конструктор, который вызывается при формировании представления из XML-файла. Он вызывается, когда представление создается из XML-файла, содержащего атрибуты представления. В этом варианте конструктора используется стиль по умолчанию (0), поэтому применяются только те значения атрибутов, которые есть в теме контекста и заданном наборе AttributeSet.

Шаг 4. Самый важный шаг в отрисовке собственного представления это переопределение метода onDraw() и реализация необходимой логики отрисовки внутри этого метода.

Метод OnDraw(canvas: Canvas?) имеет параметр Canvas (холст), с помощью которого компонент представления может отрисовывать себя. Для рисования на холсте необходимо создать объект Paint.

Как правило, процесс рисования определяется двумя аспектами:

  • что рисовать (определяется объектом Canvas);

  • как рисовать (определяется объектом Paint).

Например, Canvas предоставляет метод для рисования линии, а Paint предоставляет методы для определения цвета этой линии. В нашем случае объект Canvas в классе CircularTextView предоставляет метод для рисования окружности, а объект Paint заполняет ее цветом. Проще говоря, Canvas определяет, какие фигуры можно нарисовать на экране, а Paint определяет свойства нарисованных фигур цвет, стиль, шрифт и т.д.

Давайте займемся кодом. Мы создаем объект Paint и присваиваем ему некоторые свойства, а затем рисуем фигуру на холсте (объект Canvas), используя наш объект Paint. Метод onDraw() будет выглядеть так:

IDE показывает предупреждение о том, что следует избегать выделения объектов во время операций отрисовки или операций с макетом. Это предупреждение возникает потому, что метод onDraw() много раз вызывается при отрисовке представления, в котором каждый раз создаются ненужные объекты. Поэтому, чтобы избежать ненужного создания объектов, мы вынесем соответствующую часть кода за пределы метода onDraw(), как показано ниже.

При выполнении отрисовки всегда помните о том, что следует повторно использовать объекты вместо создания новых. Ваша IDE может указать на потенциальные проблемы, но полагаться на нее не стоит. Например, она не сможет отследить случай, когда объекты создаются внутри методов, вызываемых из метода onDraw(). Поэтому лучше проверять все самостоятельно.

Шаг 5. Мы закончили с рисованием. Теперь давайте внесем этот класс представления в XML.

Добавьте этот XML-макет в вашу активность (Activity) и запустите приложение. Вот что будет на экране.

Выглядит неплохо, правда? Теперь сделаем так, чтобы значение динамическому свойству цвета в circlePaint назначалось из активности, а также добавим контур к кружку. Для этого в классе CircularTextView необходимо создать несколько методов-сеттеров, чтобы можно было вызывать эти методы и устанавливать свойства динамически.

Для начала давайте реализуем настройку цвета отрисовки. Для этого создадим сеттер, как показано ниже.

fun setSolidColor(color: String) {    solidColor = Color.parseColor(color)    circlePaint?.color = solidColor}

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

circularTextView.setSolidColor("#FF0000")

Неплохо, правда? Теперь давайте добавим контур к кружку. Контур будет задаваться двумя входными параметрами: шириной линии контура и ее цветом. Чтобы задать цвет линии контура, нам нужно создать объект Paint точно так же, как мы это делали для кружка. Чтобы задать ширину линии контура, мы создадим переменную, установим для нее нужное значение и используем его в методе onDraw(). Полный код будет выглядеть так:

Теперь в активности можно динамически настраивать эти атрибуты нужным образом.

Далее давайте запустим приложение, устанавливая различные цвета для нашего виджета.

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

Для начала создадим файл с именем attrs.xml в папке values. Этот файл будет содержать все атрибуты для различных представлений, которые мы создаем сами. В приведенном ниже примере у нашего представления под названием CircularTextView имеется атрибут ct_circle_fill_color, который принимает значение цвета. Аналогичным образом мы можем добавить и другие атрибуты.

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

val typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CircularTextView)circlePaint?.color = typedArray.getColor(R.styleable.CircularTextView_ct_circle_fill_color,Color.BLUE)typedArray.recycle()

Теперь просто переходим к XML-макету и устанавливаем значение свойства, соответствующее нужному цвету, после чего запускаем приложение. На выходе мы увидим нужный результат.

app:ct_circle_fill_color="@color/green"

В моем случае результат был таким:

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

Обновление представления

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

invalidate()

invalidate() это метод, который инициирует принудительную перерисовку определенного представления. Проще говоря, метод invalidate() следует вызывать в случае, когда требуется изменение внешнего вида представления.

requestLayout()

Если в какой-то момент происходит изменение состояния представления, то метод requestLayout() сообщает системе представлений, что необходимо сделать перерасчет фаз измерение (Measure) и макет (Layout) для данного представления (измерение макет рисование). Проще говоря, метод requestLayout() следует вызывать в случае, когда требуется изменение границ представления.

Теперь, я надеюсь, вы знаете в общих чертах, как создавать собственные представления. Чтобы они демонстрировали отличную производительность, необходимо освоить все описанные здесь методы.

Благодарю за внимание!


- Узнать подробнее о курсе "Android Developer. Professional".


- Записаться на открытый вебинар "Пишем gradle plugin".

Подробнее..

Категории

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

  • Имя: Макс
    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