name="AppTheme"
parent="
Theme.AppCompat.Light.DarkActionBar
">
name="colorPrimary">@color/colorPrimary
name="colorPrimaryDark">@color/colorPrimaryDark
name="colorAccent">@color/colorAccent
name="AppTheme"
parent="Theme.AppCompat.Light.DarkActionBar">
name="StrongBeatBoxButton"
parent="@style/BeatBoxButton">
- bold
name="AppTheme"
parent="Theme.AppCompat.Light.DarkActionBar">
...
name="AppTheme"
parent="Theme.AppCompat
.Light.DarkActionBar
">
...
name="AppTheme"
parent="Theme.AppCompat">
name="colorPrimary">@color/
colorPrimaryred<
/item>
name="colorPrimaryDark">@color/
colorPrimary
Darkdark_red
name="colorAccent">@color/
colorAccentgray
item>
name="Theme.AppCompat"
parent="Base.Theme.AppCompat" />
Тема
Theme.AppCompat
наследует
атрибуты
от
Base.Theme.AppCompat
. Интересно, что
Theme.AppCompat
не
переопределяет никакие атрибуты, а только содержит ссылку
на своего родителя.
Щелкните мышью по
Base.Theme.AppCompat
с нажатой
клавишей (
Ctrl
). Android Studio сообщит, что тема уточняется
по ресурсам. Существует несколько разных версий этой темы в
зависимости от используемой версии Android.
name="Base.Theme.AppCompat"
parent="Base.V7.Theme.AppCompat">
name="Base.V7.Theme.AppCompat"
parent="Platform.AppCompat">
-
androidx.appcompat.app.AppCompatVie
wInflater
- false
- true
name="windowActionBarOverlay">false
name="Platform.AppCompat"
parent="android:Theme.Holo">
name="android:windowNoTitle">true
name="android:windowActionBar">false
- ?
attr/buttonBarStyle
- ?
attr/buttonBarButtonStyle
name="android:borderlessButtonStyle">?
attr/borderlessButtonStyle
...
name="Platform.AppCompat"
parent="android:Theme.Holo">
...
...
name="android:windowBackground">@color/backgrou
nd_material_dark
name="AppTheme"
parent="Theme.AppCompat">
- @color/red
name="colorPrimaryDark">@color/dark_red
- @color/gray
-
name="android:windowBackground">@color/soothing
_blue
name="Base.V7.Theme.AppCompat"
parent="Platform.AppCompat">
...
name="buttonStyle">@style/Widget.AppCompat.Butt
on
name="buttonStyleSmall">@style/Widget.AppCompat
.Button.Small
...
name="Widget.AppCompat.Button"
parent="Base.Widget.AppCompat.Button"/>
Стиль
Widget.AppCompat.Button
самостоятельно никакие
атрибуты не определяет. Чтобы просмотреть его содержимое,
перейдите к его родителю. Вы увидите, что базовый стиль
существует
в
двух
версиях.
Выберите
версию
values/values.xml
.
name="Base.Widget.AppCompat.Button"
parent="android:Widget">
name="android:background">@drawable/abc_btn_def
ault_mtrl_shape
- ?
android:attr/textAppearanceButton
- 48dip
- 88dip
- true
- true
name="android:gravity">center_vertical|center_h
orizontal
name="AppTheme"
parent="Theme.AppCompat">
name="colorPrimary">@color/red
name="AppTheme"
parent="Theme.AppCompat">
name="colorPrimary">@color/red
name="colorPrimaryDark">@color/dark_red
name="colorAccent">@color/gray
name="android:windowBackground">@color/soothing
_blue
name="BeatBoxButton"
parent="Widget.AppCompat.Button">
name="android:background">@color/dark_bluem>
name="Platform.AppCompat"
parent="android:Theme.Holo">
...
name="AppTheme"
parent="Theme.AppCompat">
...
name="BeatBoxButton"
parent="Widget.AppCompat.Button">
name="AppTheme"
parent="Theme.AppCompat">
...
name="BeatBoxButton"
parent="Widget.AppCompat.Button">
name="android:background">@drawable/button_beat
_box_normal
-
name="android:background">@drawable/button_beat
_box
Для любознательных: управление зависимостями
FlickrFetchr
предоставляет
слой
абстракции
над
источником метаданных фото Flickr. Другие компоненты
(такие как
PhotoGalleryFragment
) используют эту
абстракцию для получения данных Flickr, не думая о том,
откуда поступают эти данные.
FlickrFetchr
сам по себе не знает, как загружать данные
JSON с помощью Flickr. Вместо этого
FlickrFetchr
берет
данные у
FlickrApi
, подключается к этой конечной точке и
выполняет фактическую работу по загрузке данных JSON.
Говорят, что
FlickrFetchr
зависит от
FlickrApi
.
Вы инициализируете
FlickrApi
внутри блока
init
во
FlickrFetchr
:
class FlickrFetchr {
...
private val flickrApi: FlickrApi
init {
val retrofit: Retrofit =
Retrofit.Builder()
.baseUrl("https://www.flickr.co
m/")
.addConverterFactory(ScalarsCon
verterFactory.create())
.build()
flickrApi
=
retrofit.create(FlickrApi::class.java)
}
fun fetchContents(): LiveData {
...
}
}
Это прекрасно работает для простых приложений, но есть
несколько вопросов, требующих рассмотрения.
Во-первых, сложно проводить с
FlickrFetchr
модульное
тестирование. Из главы 20 нам известно, что целью модульного
теста является проверка поведения класса и его
взаимодействия с другими классами. Чтобы правильно
провести модульный тест
FlickrFetchr
, вам нужно
изолировать его от реального
FlickrApi
. А это сложно, если
вообще возможно, поскольку
FlickrApi
инициализируется
внутри блока инициализации
FlickrFetchr
.
Следовательно, нет никакой возможности предоставить
FlickrApi
во
FlickrFetchr
для тестирования. Это проблема,
поскольку
любой
тест,
запущенный
с
функцией
fetchContents()
, будет создавать сетевые запросы. Успех
ваших тестов будет зависеть от состояния сети и доступности
внутреннего API Flickr на момент запуска теста.
Вторая проблема заключается в том, что создавать
экземпляры
FlickrApi
сложно. Вы должны создать и
настроить экземпляр
Retrofit
, прежде чем сможете создать
экземпляр
FlickrApi
. Эта реализация требует от вас
дублирования пяти строк кода конфигурации Retrofit в любом
месте, где вы хотите создать экземпляр
FlickrApi
.
Наконец, создание нового экземпляра
FlickrApi
везде, где
вы хотите его использовать, приводит к появлению
чрезмерного числа объектов. Создание объекта стоит дорого,
особенно с учетом ограниченности ресурсов мобильных
устройств. Везде, где это целесообразно, вы должны совместно
использовать экземпляры класса в вашем приложении и
избегать ненужного выделения объектов.
FlickrApi
является
идеальным кандидатом для совместного использования, так
как у нее нет переменного состояния экземпляров.
Инъекция зависимостей (или DI) — это шаблон
проектирования, который решает эти проблемы путем
централизации логики создания зависимостей, таких как
FlickrApi
, и предоставления зависимостей классам, которые
в них нуждаются. Применив этот подход к PhotoGallery, вы
можете
легко
передать
экземпляр
FlickrApi
во
FlickrFetchr
каждый раз при создании нового экземпляра
FlickrFetchr
. Использование DI позволит вам:
• инкапсулировать логику инициализации
FlickrApi
в общем
месте за пределами
FlickrFetchr;
• использовать синглтон
FlickrApi
во всем приложении;
• заменить имитационную версию
FlickrApi
при модульном
тестировании.
Применение шаблона DI к
FlickrFetchr
может выглядеть
примерно так:
class FlickrFetchr(flickrApi: FlickrApi) {
fun fetchContents(): LiveData {
...
}
}
Обратите внимание, что в DI синглтоны используются не
для всех зависимостей. Во
FlickrFetchr
передается
экземпляр
FlickrApi
.
Этот
механизм
построения
FlickrFetchr
дает гибкость для предоставления нового
экземпляра или общего экземпляра
FlickrApi
, основанного
на вашем случае использования.
DI — это обширная тема с множеством аспектов, и разговор
о ней лежит далеко за пределами Android. Здесь мы лишь
затронули ее. Концепции DI посвящены целые книги, и
существует множество библиотек, облегчающих реализацию DI.
Если вы хотите использовать DI в своем приложении, стоит
подумать об использовании одной из этих библиотек. Это
поможет вам сократить количество кода, который придется
писать для реализации шаблона.
На момент написания этой книги официальной
рекомендуемой Google библиотекой для реализации DI на
Android является Dagger 2. Подробную документацию, примеры
кода и обучающие материалы по DI с использованием Dagger 2
вы можете найти по адресу
Do'stlaringiz bilan baham: |