-
-
Notifications
You must be signed in to change notification settings - Fork 4
Android Jetpack
Important
ВНИМАНИЕ!
ЭТОТ РАЗДЕЛ БОЛЬШЕ НЕ ПОДДЕРЖИВАЕТСЯ!
РОАДМАП ПЕРЕЕХАЛ В NOTION
Android Jetpack
Набор библиотек и инструментов, созданный командой Google для упрощения разработки под Android. Jetpack-библиотеки используют пакет androidx.*. Support-библиотеки также стали частью Jetpack
Dependency injection with Hilt
Достоинства: валидация графа в момент компиляции. Если приложение запустилось, то как минимум всех зависимостей хватает.
Недостатки: кодогенерация, замедляющая время сборки
@HiltAndroidApp
Запускает генерацию кода Hilt. Базовый класс приложения служит контейнером зависимостей на уровне приложения
@AndroidEntryPoint
Создает отдельный компонент Hilt для класса и предоставляет для него зависимости. Поддерживает: Activity, Fragment, View, Service, BroadcastReceiver, ViewModel
@HiltViewModel
Предоставляет зависимости для ViewModel
@Inject
Базовая аннотация, с помощью которой запрашивается зависимость
@Module
Информирует Hilt о том, как предоставлять зависимости определенных типов
@InstallIn
Информирует в каком классе Android будет использоваться или устанавливаться каждый модуль. SingletonComponent (Application), ViewModelComponent (ViewModel), ActivityComponent (Activity), FragmentComponent (Fragment), ViewComponent (View), ServiceComponent (Service)
@Binds
Сообщает Hilt какую реализацию использовать, когда ему нужно предоставить реализацию интерфейса
@Provide
Сообщает Hilt о том, как предоставлять экземпляры этого типа. Внедрение зависимостей в конструктор невозможно, если класс получен из внешней библиотеки (Room, Retrofit)
@Qualifier
Позволяет предоставлять различные реализации одного и того же типа в качестве зависимостей
@ApplicationContext
Переопределенный квалификатор контекста приложения
@ActivityContext
Переопределенный квалификатор контекста активити
Component scopes
По умолчанию все привязки в Hilt не имеют области действия. Это означает, что каждый раз, когда ваше приложение запрашивает привязку, Hilt создает новый экземпляр нужного типа. Hilt позволяет привязать область действия к определенному компоненту. Hilt создает привязку с ограниченной областью только один раз для каждого экземпляра компонента, на который распространяется привязка, и все запросы для этой привязки используют один и тот же экземпляр
Android class | Generated component | Scope |
---|---|---|
Application | SingletonComponent | @Singleton |
Activity | ActivityRetainedComponent | @ActivityRetainedScoped |
ViewModel | ViewModelComponent | @ViewModelScoped |
Activity | ActivityComponent | @ActivityScoped |
Fragment | FragmentComponent | @FragmentScoped |
View | ViewComponent | @ViewScoped |
View annotated with @WithFragmentBindings | ViewWithFragmentComponent | @ViewScoped |
Service | ServiceComponent | @ServiceScoped |
Отложенная инициализация
Одна из распространенных наших разработческих проблем — это долгий старт приложения. Обычно причина в одном — мы слишком много всего грузим и инициализируем при старте. Кроме того, Dagger2 строит граф зависимостей в основном потоке. И часто далеко не все конструируемые Даггером объекты нужны сразу же. Поэтому библиотека дает нам возможность отложить инициализацию объекта до первого вызова с помощью интерфейсов Provider<>
и Lazy<>
Provider<>
До первого вызова get() Dagger не инициализирует данную зависимость. Но при каждом последующем вызове get() будет создаваться новый экземпляр зависимости. Provider нужен, когда мы провайдим какую-то мутабельную зависимость, меняющую свое состояние в течении времени, и при каждом обращении нам необходимо получать актуальное состояние
@Inject lateinit var dependency: Provider<Dependency>
dependency.get()
Lazy<>
Dagger инициализирует зависимость при первом вызове get(). Далее Dagger кэширует проинициализированное значение зависимости и при повторном вызове get() выдает закэшированный объект. Если мы к зависимости добавим аннотацию @Singleton, то объект будет кешироваться для всего дерева зависимостей
@Inject lateinit var dependency: Lazy<Dependency>
dependency.get()
LiveData
Класс, который хранит данные и реализует паттерн Observable. LiveData учитывает жизненный цикл компонентов Android
MediatorLiveData
Подкласс LiveData, который может наблюдать за другими объектами LiveData и реагировать на изменения в них. Объединяет несколько LiveData в один объект
observe
Добавляет наблюдателя в список, события отправляются в главный поток, если у LiveData уже установлены данные, они будут доставлены наблюдателю. Будет получать события, пока находится в состоянии STARTED или RESUMED, будет автоматически удален при переходе в состояние DESTROYED. Если наблюдатель не активен - данные не доставляются, когда станет активен получит последние доступные данные
observeForever
Добавляет наблюдателя в список, метот работает как observe, который всегда активен. Будет получать все события и никогда не будет автоматически удален. Чтобы прекратить наблюдение нужно вручную вызвать removeObserver. Используется в тестах
viewLifecycleOwner
Класс Fragment реализует интерфейс LifecycleOwner, поэтому в метод LiveData.observe() первым параметром можно передавать this, также как в Activity. Но жизненный цикл View отличается от жизненного цикла фрагмента. Ивент из LiveData может прийти после вызова метода onDestroyView(). В этом случае View фрагмента занулится, и при попытке обновления UI будет брошен NullPointerException. Поэтому рекомендуется использовать метод viewLifecycleOwner, который возвращает объект LifecycleOwner, ассоциированный с жизненным циклом View
setValue(T)
Устанавливает значение в LiveData. Если есть активные наблюдатели, значение будет отправлено им. Этот метод должен вызываться из главного потока
postValue(T)
Устанавливает значение в LiveData. Метод можно вызвать из фонового потока
map
Применяет заданную функцию на главном потоке к каждому значению, выдаваемому источником LiveData, и возвращает LiveData, который выдает результат преобразования
switchMap
Преобразует каждый элемент исходной LiveData в новый промежуточный LiveData-стрим
distinctUntilChanged
Возвращает новое значение, только если оно != предыдущему, для сравнения используется equals()
private val numberMutableLiveData: MutableLiveData<Number> = MutableLiveData(0)
val numberLiveData: LiveData<Number> = numberMutableLiveData
numberMutableLiveData.value = 100
numberMutableLiveData.postValue(100)
numberLiveData.observe(viewLifecycleOwner, Observer { number: Number ->
println(number)
})
liveData
Создает LiveData со значениями, полученными в данном билдере
val data: LiveData<Int> = liveData {
delay(3000)
emit(3)
}
asLiveData
Создает LiveData со значениями, созданными из Flow
val valueLiveData: LiveData<Boolean> = liveData {
emitSource(interactor.getValueFlow().asLiveData())
}
Navigation
Библиотека для навигация между экранами в приложении. Поддерживает appbar и drawer
Paging
Библиотека для постраничной загрузки больших наборов данных из сети или локального хранилища. Позволяет приложению более эффективно использовать пропускную способность сети и системные ресурсы. Paging разработана для удобной интеграции с другими компонентами Jetpack
Room
Представляет уровень абстракции над SQLite. Проверяет SQL-запросы во время компиляции, удобные аннотации, которые минимизируют повторяющийся и подверженный ошибкам шаблонный код, упрощенная миграция
Types
TEXT
for String
INTEGER
for Int, Long, Boolean
REAL
for Double, Float
@Dao
Data Access Object. Абстрактный класс или интерфейс, в котором описаны методы запросов к бд. Во время компиляции Room генерирует реализацию этого класса. Рекомендуется иметь несколько классов Dao в зависимости от таблиц, с которыми они связаны
@Entity
Маркирует класс как сущность, которая будет иметь сопоставленную таблицу SQLite в бд. Должен иметь PrimaryKey
Migration
Чтобы узнать что за ошибки в миграции необходимо взять из Android Logcat Expected и Found версии миграции и сравнить их через сайт. В Found необходимо заранее убрать ''null'', заменив на 'null'. Как правильно создана таблица можно увидеть в файле миграции, который генерирует студия
Flow
Чтобы Room реагировал на удаление данных из таблицы нужно использовать аннотацию @Delete
How ViewModels survive configuration changes
Как ViewModel переживает пересоздание фрагмента?
ViewModel
Предназначен для хранения данных, связанных с UI и управления ими с учетом жизненного цикла. Позволяет данным сохраняться после изменения конфигурации. ViewModel живет в памяти процесса приложения и уничтожается вместе с ним
• viewModel уничтожается вместе с уничтожением activity (знает о lifecycle)
• можно привязать любое количество viewModel к activity/fragment
• fragment не хранит никакие данные, все доставляется в viewmodel (assisted inject), fragment вызывает методы viewmodel напрямую
• можно использовать viewmodel как переменную в XML-разметке с помощью databinding
AndroidViewModel
ViewModel с контекстом приложения
Getting started with WorkManager
Schedule tasks with WorkManager
WorkManager
Инструмент планирования отложенных асинхронных задач, которые должны надежно выполняться
• для описания задачи необходимо наследоваться от класса Worker и определить метод doWork(). Код внутри метода doWork() будет выполнен в рабочем потоке WorkManager
• работа может быть одноразовой OneTimeWorkRequestBuilder или периодической PeriodicWorkRequestBuilder
• ограничение на запуск: раз в 15 мин
• условия запуска: charging, battery level, network/wi-fi connected, device sleep, storage not low
• поддерживает цепочки задач и параллельное выполнение
• под капотом JobScheduler (API 23+), GcmNetworkManager/AlarmManager (API 14-22, has Google Play Services)
• использоватьдля критических задач, которые должны пережить смерть процесса приложения
• хранится в sqlite и может запуститься в указанное время после перезагрузки приложения
View Binding
Привязка, упрощающая взаимодействие с view, объявленными в XML. Объявляется в Gradle-модуле и создает класс привязки для каждого файла разметки. Экземпляр класса привязки содержит прямые ссылки на все представления, имеющие идентификатор в соответствующем макете
Activity
private lateinit var binding: ActivityViewBinding
override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivityViewBinding.inflate(layoutInflater)
}
Fragment
private var _binding: FragmentViewBinding? = null
private val binding: FragmentViewBindingget get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = DialogTransactionDetailBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
View
private val binding = CustomVidgetBinding.inflate(LayoutInflater.from(context), this, true)
ViewHolder
override fun createViewHolder(parent: ViewGroup): Holder {
val binding = ListItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return Holder(binding)
}
class Holder(
private val binding: ListItemBinding
): ViewHolder(binding.root) {
Home • Interviews • Android Architecture • Android Jetpack • Android Jetpack Compose • Android Releases • Android SDK • Android Views • Basic • Design • Git • GitHub • Gradle • Java • Kotlin • Kotlin Coroutines • RxJava