-
-
Notifications
You must be signed in to change notification settings - Fork 4
Android Jetpack
Android Jetpack
Набор библиотек и инструментов, созданный командой Google для упрощения разработки под Android. Jetpack-библиотеки используют пакет androidx.*. Support-библиотеки также стали частью Jetpack
Compose
Cовременный декларативный набор инструментов для создания пользовательского интерфейса Android. Упрощает проектирование, связанное с созданием и обновлением пользовательских интерфейсов
Accompanist
Группа библиотек, целью которых является дополнение Jetpack Compose функциями, которые обычно требуются разработчикам, но пока недоступны
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 |
Features
Валидация графа в момент компиляции. Если приложение запустилось, то как минимум всех зависимостей хватает
Отложенная инициализация
Одна из распространенных наших разработческих проблем — это долгий старт приложения. Обычно причина в одном — мы слишком много всего грузим и инициализируем при старте. Кроме того, 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 в один объект
В чем разница между LiveData.observe() и LiveData.observeForever()?
Метод LiveData.observe()
принимает два параметра: lifecycleOwner
и observer
. LiveData следит за жизненным циклом lifecycleOwner, доставляет ивенты только активным подписчикам и удаляет подписчиков, которые перешли в состояние DESTROYED. Метод LiveData.observeForever()
принимает один параметр: observer. Этот метод реализует классическую схему шаблона Observer-Observable. Ивенты доставляются подписчику всегда, незавимо от его состояния. Также существует симметричный метод LiveData.removeObserver(), созданный для ручного удаления подписчиков. Обычно метод observeForever() используется в тестах, а не в коде приложения.
Как правильно подписываться на LiveData во фрагментах?
Класс Fragment реализует интерфейс LifecycleOwner, поэтому в метод LiveData.observe() первым параметром можно передавать this, также как в Activity. Но жизненный цикл View отличается от жизненного цикла фрагмента. Ивент из LiveData может прийти после вызова метода onDestroyView(). В этом случае View фрагмента занулится, и при попытке обновления UI будет брошен NullPointerException. Поэтому рекомендуется использовать метод getViewLifecycleOwner(), который возвращает объект LifecycleOwner, ассоциированный с жизненным циклом View.
В этом случае подписываться необходимо в методе onCreateView(), а после вызова onDestroyView() подписчик перейдет в состояние DESTROYED и автоматически отпишется от обновлений LiveData.
Какие трансформации возможны на LiveData?
Трансформации LiveData производятся утилитным классом Transformations. Этот класс имеет три статических функции:
- map() преобразует каждый элемент LiveData. Аналогичен функции map на списках и в RxJava.
- switchMap() преобразует каждый элемент исходной LiveData в новый промежуточный LiveData-стрим. Аналогичен switchMap() в RxJava.
- distinctUntilChanged() не принимает параметров и возвращает новый объект LiveData, который уведомляет об обновлении только если следующий элемент не равен предыдущему. Для сравнения используется equals().
Помимо использования статических методов класса Transformations, можно подключить KTX расширения библиотеки LiveData и получить методы в качестве extension-функций.
Как объединить несколько LiveData?
Для объединения нескольких LiveData используется класс MediatorLiveData. Метод MediatorLiveData.addSource() принимает два параметра: исходный LiveData и функциональный интерфейс Observer, который вызывается при обновлении исходного LiveData. Для объединения двух LiveData нужно добавить исходные LiveData методом addSource(), и на каждый ивент обновлять значение в MediatorLiveData.
Пример на картинке.
Как создать кастомный transform-оператор LiveData?
Кастомный transform-оператор для LiveData создается следующим образом:
- Объявляется extension-функция на классе LiveData, которая принимает функциональный тип как параметр.
- Создается MediatorLiveData.
- На MediatorLiveData вызывается метод addSource(). В этот метод первым аргументом передается LiveData, на который вызывается transform-оператор (т.е. this).
- Вторым аргументом реализуется логика transform-оператора, с использованием функции, переданной в качестве параметра.
What is Difference between setValue() and PostValue() in MutableLiveData?
Метод setValue должен вызываться из основного потока (main thread). Если нужно установить значение из фонового потока следует использовать postValue().
Чтобы при возврате на фрагмент с пагинацией восстановилась правильная позиция прокрутки нужно избавиться от flatMapLatest. viewLifecycleOwner.addRepeatingJob(Lifecycle.State.CREATED) в некоторых случаях может ломать сохранение позиции recyclerview.
Room
Room реализует уровень абстракции над базой данных SQLite. API библиотеки Room заточено под использование для кэширования данных, полученных с бэкенда. Сегодня Room – это стандарт де-факто при работе с базой данных в Android-приложениях.
Room Migration
Check Room Diff
Чтобы узнать что за ошибки в миграции необходимо взять из Android Logcat Expected и Found версии миграции и сравнить их через сайт. В Found необходимо заранее убрать ''null'', заменив на 'null'. Как правильно создана таблица можно увидеть в файле миграции, который генерирует студия.
Type | Summary |
---|---|
TEXT | val it: String |
INTEGER | val it: Int, val it: Long, val it: Boolean |
REAL | val it: Double, val it: Float |
ViewModel
Предназначен для хранения данных, связанных с Ui и управления ими с учетом жизненного цикла. Позволяет данным сохраняться после изменения конфигурации
AndroidViewModel
ViewModel с контекстом приложения
Является ли ViewModel заменой onSaveInstanceState()?
Нет. ViewModel и onSaveInstanceState() используются в разных ситуациях. onSaveInstanceState() предназначен для сохранения небольшого количества данных, которые позволяют восстановить состояние UI в следующих случаях: остановка процесса приложения для восстановления ресурсов памяти и изменение конфигурации. Не следует использовать onSaveInstanceState() для сохранения больших массивов данных. ViewModel переживает только изменение конфигурации, но уничтожается при остановке процесса. Это делает ViewModel менее универсальным механизмом, чем onSaveInstanceState(), но позволяет сохранять большие объекты во время изменения конфигурации и пересоздания активити.
Как ViewModel переживает пересоздание активити?
FragmentActivity наследуется от класса ComponentActivity, который реализует метод getViewModelStore(): ViewModelStore интерфейса ViewModelStoreOwner. ComponentActivity использует переопределенный метод Activity.onRetainNonConfigurationInstance() для сохранения объекта ViewModelStore. Этот метод вызывается между onStop() и onDestroy() и возвращает произвольный объект, который сохраняется системой во время пересоздания активити. При вызове getViewModelStore(), ComponentActivity получает сохраненный ViewModelStore с помощью метода getLastNonConfigurationInstance().
Как ViewModel переживает пересоздание фрагмента?
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