Skip to content

Android Jetpack

Michael Bely edited this page Apr 29, 2024 · 128 revisions

Important

ВНИМАНИЕ!
ЭТОТ РАЗДЕЛ БОЛЬШЕ НЕ ПОДДЕРЖИВАЕТСЯ!
РОАДМАП ПЕРЕЕХАЛ В NOTION

Android Jetpack
Набор библиотек и инструментов, созданный командой Google для упрощения разработки под Android. Jetpack-библиотеки используют пакет androidx.*. Support-библиотеки также стали частью Jetpack

Hilt

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

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

Navigation
Библиотека для навигация между экранами в приложении. Поддерживает appbar и drawer

Paging

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

Room

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

ViewModel

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 с контекстом приложения

WorkManager

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 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) {
Clone this wiki locally