Перейти к содержимому

Вопросы и ответы — Android Developer — Компоненты и View

onCreate():
Вызывается при создании активности. Это место для инициализации основных компонентов активности, таких как пользовательский интерфейс (UI) и данных.

onStart():
Вызывается, когда активность становится видимой для пользователя.

onResume():
Вызывается, когда активность начинает взаимодействовать с пользователем. Активность находится на переднем плане и пользователь может с ней взаимодействовать.

onPause():
Вызывается, когда активность уходит на задний план, но все еще видима. Используется для сохранения данных, остановки анимаций или других ресурсов, которые не должны продолжаться в фоновом режиме.

onStop():
Вызывается, когда активность больше не видима для пользователя. Это происходит, когда активность закрывается или переходит в фоновый режим.

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

onRestart():
Вызывается, когда активность была остановлена и снова запускается перед onStart().

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.util.Log


class MainActivity : AppCompatActivity() {

    private val TAG = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.d(TAG, "onCreate called")
    }

    override fun onStart() {
        super.onStart()
        Log.d(TAG, "onStart called")
    }

    override fun onResume() {
        super.onResume()
        Log.d(TAG, "onResume called")
    }

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "onPause called")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "onStop called")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy called")
    }

    override fun onRestart() {
        super.onRestart()
        Log.d(TAG, "onRestart called")
    }
    
}

onAttach():
Вызывается, когда фрагмент присоединяется к своей активности.

onCreate():
Вызывается при создании фрагмента. Здесь можно выполнять инициализацию, которая не зависит от пользовательского интерфейса.

onCreateView():
Вызывается для создания пользовательского интерфейса фрагмента. Здесь нужно инфлейтить (раздувать) макет фрагмента.

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

onActivityCreated():
Вызывается, когда метод onCreate активности завершён.

onStart():
Вызывается, когда фрагмент становится видимым для пользователя.

onResume():
Вызывается, когда фрагмент становится активным и готов к взаимодействию с пользователем.

onPause():
Вызывается, когда фрагмент уходит на задний план, но все еще видим.

onStop():
Вызывается, когда фрагмент больше не виден.

onDestroyView():
Вызывается перед уничтожением представления фрагмента.

onDestroy():
Вызывается перед уничтожением фрагмента.

onDetach():
Вызывается, когда фрагмент отсоединяется от своей активности.

import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.util.Log
import androidx.fragment.app.Fragment


class ExampleFragment : Fragment() {

    private val TAG = "ExampleFragment"

    override fun onAttach(context: Context) {
        super.onAttach(context)
        Log.d(TAG, "onAttach called")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "onCreate called")
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        Log.d(TAG, "onCreateView called")
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_example, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Log.d(TAG, "onViewCreated called")
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        Log.d(TAG, "onActivityCreated called")
    }

    override fun onStart() {
        super.onStart()
        Log.d(TAG, "onStart called")
    }

    override fun onResume() {
        super.onResume()
        Log.d(TAG, "onResume called")
    }

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "onPause called")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "onStop called")
    }

    override fun onDestroyView() {
        super.onDestroyView()
        Log.d(TAG, "onDestroyView called")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy called")
    }

    override fun onDetach() {
        super.onDetach()
        Log.d(TAG, "onDetach called")
    }
    
}

Constructor:
при создании пользовательского представления вы должны сначала переопределить конструктор, который инициализирует View, в нем можно проводить необходимые вычисления

onAttachedToWindow():
Вызывается, когда View присоединяется к окну.

onMeasure(int widthMeasureSpec, int heightMeasureSpec):
Вызывается для определения размера View и его дочерних элементов.

onLayout(boolean changed, int left, int top, int right, int bottom):
Вызывается для установки размера и положения View и его дочерних элементов.

onDraw(Canvas canvas):
Вызывается для отрисовки содержимого View.

onDetachedFromWindow():
Вызывается, когда View отсоединяется от окна.

import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.util.Log
import android.view.View


class CustomView @JvmOverloads constructor(
    context: Context, 
    attrs: AttributeSet? = null, 
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val TAG = "CustomView"

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        Log.d(TAG, "onAttachedToWindow called")
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        Log.d(TAG, "onMeasure called")
        // Example: Set the size to be exactly 200x200 pixels
        setMeasuredDimension(200, 200)
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        Log.d(TAG, "onLayout called")
        // Layout the child views if any
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        Log.d(TAG, "onDraw called")
        // Draw the custom view content
        canvas?.drawColor(android.graphics.Color.RED)
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        Log.d(TAG, "onDetachedFromWindow called")
    }
    
}

onCleared():
Вызывается, когда ViewModel уничтожается. Здесь можно освобождать ресурсы, такие как отмена задач, закрытие потоков и т.д.

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel


class MyViewModel : ViewModel() {
  
    override fun onCleared() {
        super.onCleared()
        // Освобождение ресурсов, если необходимо
    }
    
}

onCreate():
Вызывается при первом создании сервиса. Здесь можно выполнять инициализацию необходимых ресурсов.

onStartCommand(Intent intent, int flags, int startId):
Вызывается при каждом запуске сервиса. Здесь выполняется основная работа сервиса.

onBind(Intent intent):
Вызывается при привязывании компонента (например, активности) к сервису с помощью bindService(). Этот метод используется только для привязанных сервисов.

onUnbind(Intent intent):
Вызывается при отсоединении компонента от привязанного сервиса.

onRebind(Intent intent):
Вызывается, если компонент повторно привязывается к сервису после вызова onUnbind().

onDestroy():
Вызывается перед уничтожением сервиса. Здесь можно освобождать ресурсы.

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log


class MyService : Service() {

    private val TAG = "MyService"

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "Service Created")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d(TAG, "Service Started")

        // Выполнение фоновой работы
        Thread(Runnable {
            try {
                // Симуляция долгой работы
                Thread.sleep(3000)
                Log.d(TAG, "Service Running")
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
            stopSelf()
        }).start()

        // Если сервис должен быть перезапущен после завершения, возвращаем START_STICKY
        return START_STICKY
    }

    override fun onBind(intent: Intent?): IBinder? {
        Log.d(TAG, "Service Bound")
        return null
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "Service Destroyed")
    }
    
}

Принципы, собранные Робертом Мартином, автором Чистой архитектуры. SOLID — это аббревиатура, обозначающая пять принципов объектно-ориентированного программирования и дизайна, которые помогают создавать гибкие, масштабируемые и легко поддерживаемые программные системы.

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

Плохой пример:

class UserService {
  
    fun registerUser(user: User) {
        // Регистрация пользователя
        println("User registered: ${user.name}")
        sendWelcomeEmail(user)
    }

    private fun sendWelcomeEmail(user: User) {
        // Отправка приветственного письма
        println("Welcome email sent to ${user.email}")
    }
    
}

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

Хороший пример:

class UserService(private val emailService: EmailService) {
  
    fun registerUser(user: User) {
        // Регистрация пользователя
        println("User registered: ${user.name}")
        emailService.sendWelcomeEmail(user)
    }
    
}

class EmailService {
  
    fun sendWelcomeEmail(user: User) {
        // Отправка приветственного письма
        println("Welcome email sent to ${user.email}")
    }
    
}

Что здесь хорошо:
Класс UserService отвечает только за регистрацию пользователя, а EmailService — за отправку писем. Это соответствует принципу единственной ответственности.

Классы должны быть открыты для расширения, но закрыты для модификации.

Плохой пример:

class DiscountService {
  
    fun applyDiscount(price: Double, customerType: String): Double {
        return if (customerType == "Regular") {
            price * 0.9
        } else if (customerType == "Premium") {
            price * 0.8
        } else {
            price
        }
    }
    
}

Что здесь плохо:
Для добавления нового типа клиента потребуется изменить класс DiscountService, что нарушает принцип открытости/закрытости.

Хороший пример:

interface DiscountPolicy {
    fun applyDiscount(price: Double): Double
}

class RegularDiscountPolicy : DiscountPolicy {
  
    override fun applyDiscount(price: Double): Double {
        return price * 0.9
    }
    
}

class PremiumDiscountPolicy : DiscountPolicy {
  
    override fun applyDiscount(price: Double): Double {
        return price * 0.8
    }
    
}

class DiscountService(private val discountPolicy: DiscountPolicy) {
  
    fun applyDiscount(price: Double): Double {
        return discountPolicy.applyDiscount(price)
    }
    
}

Что здесь хорошо:
Класс DiscountService открыт для расширения (можно добавить новые типы скидок), но закрыт для модификации (не нужно изменять существующий код для добавления новых скидок).

Объекты подклассов должны заменять объекты базового класса без изменения правильности программы.

Плохой пример:

open class Bird {
  
    open fun fly() {
        println("Bird is flying")
    }
    
}

class Penguin : Bird() {
  
    override fun fly() {
        throw UnsupportedOperationException("Penguins can't fly")
    }
    
}

Что здесь плохо:
Подкласс Penguin нарушает принцип LSP, так как он не может заменить базовый класс Bird без изменения логики (метод fly не применим к пингвинам).

Хороший пример:

open class Bird {
  
    open fun move() {
        println("Bird is moving")
    }
    
}

class Penguin : Bird() {
  
    override fun move() {
        println("Penguin is swimming")
    }
    
}

Что здесь хорошо:
Подкласс Penguin заменяет базовый класс Bird, не нарушая его логики. Метод move может означать различное поведение для разных птиц.

Клиенты не должны зависеть от интерфейсов, которые они не используют.

Плохой пример:

interface Worker {
    fun work()
    fun eat()
}

class HumanWorker : Worker {
  
    override fun work() {
        println("Human is working")
    }

    override fun eat() {
        println("Human is eating")
    }
    
}

class RobotWorker : Worker {
  
    override fun work() {
        println("Robot is working")
    }

    override fun eat() {
        throw UnsupportedOperationException("Robots don't eat")
    }
    
}

Что здесь плохо:
Интерфейс Worker включает методы, которые не применимы ко всем его реализациям. Роботы не должны реализовывать метод eat.

Хороший пример:

interface Worker {
    fun work()
}

interface Eater {
    fun eat()
}

class HumanWorker : Worker, Eater {
  
    override fun work() {
        println("Human is working")
    }

    override fun eat() {
        println("Human is eating")
    }
    
}

class RobotWorker : Worker {
  
    override fun work() {
        println("Robot is working")
    }
    
}

Что здесь хорошо:
Интерфейсы Worker и Eater разделены по функциональности. Классы реализуют только те интерфейсы, которые им действительно нужны.

Модули верхнего уровня не должны зависеть от модулей нижнего уровня; оба должны зависеть от абстракций.

Плохой пример:

class EmailService {
  
    fun sendEmail(message: String) {
        println("Sending email: $message")
    }
    
}

class NotificationService {
  
    private val emailService = EmailService()

    fun notify(message: String) {
        emailService.sendEmail(message)
    }
    
}

Что здесь плохо:
NotificationService жестко зависит от конкретного класса EmailService, что делает его трудно изменяемым и тестируемым.

Хороший пример:

interface MessageService {
    fun sendMessage(message: String)
}

class EmailService : MessageService {
  
    override fun sendMessage(message: String) {
        println("Sending email: $message")
    }
    
}

class SmsService : MessageService {
  
    override fun sendMessage(message: String) {
        println("Sending SMS: $message")
    }
    
}

class NotificationService(private val messageService: MessageService) {
  
    fun notify(message: String) {
        messageService.sendMessage(message)
    }
    
}

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

Обычно приложение разделяется на 3 слоя, которые могут находиться в разных модулях- Presentation, Domain, Data:

Presentation:
UI, ViewModel и другие подобные классы

Domain / Business Logic:
интерфейсы UseCase и их реализации
интерфейсы репозиториев (без реализации)
локальные модели данных, с которыми работает приложение

Data:
реализации интерфейсов репозиториев
сетевые модели данных
модели базы данных
интерфейсы Newtork API

Обычно зависимости в Gradle настраиваются таким образом:

Presentation:
implementation project(“:domain”)
implementation project(“:data”)

Data:
implementation project(‘:domain’)

Domain:
// no

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

Fragment:
это компоненты, которые могут быть использованы внутри активностей для создания более сложного пользовательского интерфейса. Фрагменты также имеют свой жизненный цикл и могут использоваться для создания переиспользуемых компонентов.

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

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

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

AndroidManifest.xml:
это файл, который содержит информацию о компонентах приложения и его настройках. Манифест приложения также определяет разрешения, необходимые для доступа к различным функциям устройства.

Model-View-Controller (MVC):
разделяет приложение на три компонента: Model, View и Controller.

Model:
Управляет данными и бизнес-логикой.

View:
Отвечает за отображение данных и взаимодействие с пользователем.

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

// Model
data class User(val name: String, val age: Int)

// Controller
class UserController(private val view: UserView) {
  
    private val user = User("John Doe", 25)
    
    fun loadUser() {
        view.showUser(user)
    }
    
}

// View
interface UserView {
    fun showUser(user: User)
}

// Activity implementing View
class MainActivity : AppCompatActivity(), UserView {
  
    private lateinit var controller: UserController
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        controller = UserController(this)
        controller.loadUser()
    }
    
    override fun showUser(user: User) {
        // Update UI with user data
        findViewById<TextView>(R.id.textView).text = "${user.name}, ${user.age}"
    }
    
}

Model-View-Presenter (MVP):
разделяет приложение на три компонента: Model, View и Presenter.

Model:
Управляет данными и бизнес-логикой.

View:
Отвечает за отображение данных и взаимодействие с пользователем.

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

// Model
data class User(val name: String, val age: Int)

// View
interface UserView {
    fun showUser(user: User)
}

// Presenter
class UserPresenter(private val view: UserView) {
  
    private val user = User("John Doe", 25)
    
    fun loadUser() {
        view.showUser(user)
    }
    
}

// Activity implementing View
class MainActivity : AppCompatActivity(), UserView {
  
    private lateinit var presenter: UserPresenter
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)      
        presenter = UserPresenter(this)
        presenter.loadUser()
    }
    
    override fun showUser(user: User) {
        // Update UI with user data
        findViewById<TextView>(R.id.textView).text = "${user.name}, ${user.age}"
    }
    
}

Model-View-ViewModel (MVVM):
разделяет приложение на три компонента: Model, View и ViewModel.

Model:
Управляет данными и бизнес-логикой.

View:
Отвечает за отображение данных и взаимодействие с пользователем.

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

// Model
data class User(val name: String, val age: Int)

// ViewModel
class UserViewModel : ViewModel() {
  
    private val _user = MutableLiveData<User>()
    val user: LiveData<User> get() = _user
  
    fun loadUser() {
        _user.value = User("John Doe", 25)
    }
    
}

// Activity
class MainActivity : AppCompatActivity() {
  
    private lateinit var viewModel: UserViewModel
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
        viewModel.user.observe(this, Observer { user ->
            // Update UI with user data
            findViewById<TextView>(R.id.textView).text = "${user.name}, ${user.age}"
        })
        viewModel.loadUser()
    }
    
}

Model-View-Intent (MVI):
основывается на концепциях реактивного программирования и уни-directional data flow.

Model:
Управляет данными и бизнес-логикой.

View:
Отвечает за отображение данных и взаимодействие с пользователем.

Intent:
Определяет намерения пользователя и направляет их в обработку.

// Model
data class User(val name: String, val age: Int)

// View State
data class UserViewState(val user: User? = null)

// Intent
sealed class UserIntent {
    object LoadUser : UserIntent()
}

// ViewModel
class UserViewModel : ViewModel() { 
  
    private val _viewState = MutableLiveData<UserViewState>()
    val viewState: LiveData<UserViewState> get() = _viewState
  
    fun processIntents(intent: UserIntent) {
        when (intent) {
            is UserIntent.LoadUser -> loadUser()
        }
    }
    
    private fun loadUser() {
        _viewState.value = UserViewState(User("John Doe", 25))
    }
    
}

// Activity
class MainActivity : AppCompatActivity() {
  
    private lateinit var viewModel: UserViewModel
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
        viewModel.viewState.observe(this, Observer { viewState ->
            viewState.user?.let { user ->
                // Update UI with user data
                findViewById<TextView>(R.id.textView).text = "${user.name}, ${user.age}"
            }
        })
        // Dispatch intent to load user
        viewModel.processIntents(UserIntent.LoadUser)
    }
    
}

MVP:
Удобен для небольших приложений. Presenter напрямую управляет View и Model.

MVVM:
Хорошо подходит для приложений с реактивным программированием и большим количеством UI-логики. ViewModel обеспечивает двустороннее связывание данных.

MVC:
Наиболее простой паттерн. Контроллер управляет взаимодействием между Model и View.

MVI:
Подходит для сложных приложений с использованием реактивного программирования и уни-directional data flow. Intent и View State делают логику приложения предсказуемой и легко тестируемой.

Android ViewModel:
это компонент архитектуры Jetpack, который помогает сохранять и управлять данными UI-компонентов (Activity, Fragment) при поворотах экрана, пересоздании приложения и других изменениях жизненного цикла приложения.
ViewModel является частью архитектурного шаблона Model-View-ViewModel (MVVM) и обычно используется в сочетании с LiveData, чтобы предоставлять актуальные данные для пользовательского интерфейса.
В ViewModel есть только один метод жизненного цикла- OnCleared()

Android SharedViewModel:
это компонент архитектуры Jetpack, который позволяет обмениваться данными между двумя или более связанными UI-компонентами (Activity, Fragment) с использованием одного и того же экземпляра ViewModel.
SharedViewModel обычно используется в случаях, когда два или более UI-компонента должны иметь доступ к одним и тем же данным, но не являются прямо связанными друг с другом. Например, в случае, когда у нас есть список элементов в одном фрагменте, а детали элемента отображаются в другом фрагменте, который находится в другой Activity.

Компоненты Android, которые позволяют приложению получать системные и пользовательские сообщения (broadcast) от других приложений, а также от самой системы.
Broadcast Receiver слушает и фильтрует сообщения, и если событие соответствует заданным фильтрам, то запускает код, связанный с этим событием.
Примеры таких событий могут включать изменение состояния сети, изменение уровня заряда батареи, получение сообщения от другого приложения и т.д.

Broadcast Receiver может использоваться для выполнения каких-либо задач на устройстве, например, для уведомления пользователя об изменениях состояния устройства или для запуска задач в фоновом режиме.
Для использования Android Broadcast Receiver необходимо создать класс, который будет расширять класс BroadcastReceiver и переопределять метод onReceive(), который будет вызываться при получении широковещательного сообщения.

class MyBroadcastReceiver : BroadcastReceiver() {
  
    override fun onReceive(context: Context?, intent: Intent?) {
        // обработка полученного сообщения
    }
    
}

Далее необходимо зарегистрировать этот Broadcast Receiver в AndroidManifest.xml файле:

<receiver android:name=".MyBroadcastReceiver">
    <intent-filter>
        <action android:name="android.intent.action.ACTION_NAME" />
    </intent-filter>
</receiver>

В данном примере MyBroadcastReceiver будет обрабатывать сообщения с действием “android.intent.action.ACTION_NAME”. При получении такого сообщения будет вызываться метод onReceive() данного Broadcast Receiver.
Также можно зарегистрировать Broadcast Receiver динамически в коде, используя методы registerReceiver() и unregisterReceiver() класса Context.

компонент архитектуры приложения в Android, который обеспечивает доступ к данным приложения извне. Он может использоваться для обмена данными между приложениями, предоставления доступа к данным из других приложений или для сохранения и извлечения данных из базы данных.
Вот пример использования Content Provider для сохранения и извлечения данных из базы данных SQLite в Android:

Определение контент-провайдера в манифесте приложения:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <application>
        ...
        <provider
            android:name=".MyContentProvider"
            android:authorities="com.example.myapp.provider"
            android:exported="false" />
    </application>
</manifest>

Создание класса MyContentProvider:
Он будет наследоваться от ContentProvider и определение базы данных и URI, которые будет использоваться для доступа к данным

class MyContentProvider : ContentProvider() {
  
    private lateinit var dbHelper: MyDatabaseHelper
  
    override fun onCreate(): Boolean {
        context?.let {
            dbHelper = MyDatabaseHelper(it)
        }
        return true
    }
    
    override fun query(
        uri: Uri,
        projection: Array<String>?,
        selection: String?,
        selectionArgs: Array<String>?,
        sortOrder: String?
    ): Cursor? {
        val db = dbHelper.readableDatabase
        val cursor = db.query(
            TABLE_NAME,
            projection,
            selection,
            selectionArgs,
            null,
            null,
            sortOrder
        )
        cursor.setNotificationUri(context?.contentResolver, uri)
        return cursor
    }
    
    override fun getType(uri: Uri): String? {
        return "vnd.android.cursor.dir/$AUTHORITY.$TABLE_NAME"
    }
    
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        val db = dbHelper.writableDatabase
        val id = db.insert(TABLE_NAME, null, values)
        context?.contentResolver?.notifyChange(uri, null)
        return ContentUris.withAppendedId(uri, id)
    }
    
    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
        val db = dbHelper.writableDatabase
        val count = db.delete(TABLE_NAME, selection, selectionArgs)
        context?.contentResolver?.notifyChange(uri, null)
        return count
    }
    
    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<String>?
    ): Int {
        val db = dbHelper.writableDatabase
        val count = db.update(TABLE_NAME, values, selection, selectionArgs)
        context?.contentResolver?.notifyChange(uri, null)
        return count
    }
    
    companion object {
        const val AUTHORITY = "com.example.myapp.provider"
        const val TABLE_NAME = "my_table"
        val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY/$TABLE_NAME")
    }
    
}

Использование контент-провайдера для доступа к данным из другого приложения:

val cursor = contentResolver.query(MyContentProvider.CONTENT_URI, null, null, null, null)

Базовый абстрактный класс, реализация которого обеспечивается системой Android. Этот класс имеет методы для доступа к специфичным для конкретного приложения ресурсам и классам и служит для выполнения операций на уровне приложения, таких, как запуск активностей, отправка широковещательных сообщений, получение намерений и прочее. От класса Context наследуются такие крупные и важные классы, как Application, Activity и Service, поэтому все его методы доступны из этих классов

Activity Context:
Создается при создании активити и уничтожается вместе с активити. Контекст — тяжелый объект. Когда говорят об утечке памяти в андроиде, имеют в виду утечку контекста, т.е. ситуацию, когда контекст активити хранится после вызова Activity.onDestroy(). Не передавайте контекст активити в другой объект, если не известно как долго этот объект проживет.

Application Context:
Синглтон. Application Context создается при создании объекта Application и живет, пока жив процесс приложения. По этой причине Application Context можно безопасно инжектить в другие синглтоны в приложении. Не рекомендуется использовать Application Context для старта активити, потому что необходимо создание новой задачи, и для layout inflation, потому что используется дефолтная тема.

Получить контекст внутри кода можно одним из следующих методов:

getBaseContext:
получить ссылку на базовый контекст

getApplicationContext:
получить ссылку на объект приложения

getContext:
внутри активности или сервиса получить ссылку на этот объект)

this:
то же, что и getContext

MainActivity.this:
внутри вложенного класса или метода получить ссылку на объект MainActivity

getActivity:
внутри фрагмента получить ссылку на объект родительской активности

Также есть разница:

getContext():
возвращает значение Context, допускающее значение NULL.

requireContext():
возвращает ненулевое значение Context или выдает исключение, если оно недоступно.

Если ваш код находится в фазе жизненного цикла, когда вы знаете, что ваш фрагмент прикреплен к контексту, просто используйте requireContext(), чтобы получить Context, а также чтобы статические анализаторы были довольны потенциальными проблемами NPE.

Если ваш код находится за пределами обычного жизненного цикла фрагмента (скажем, асинхронного обратного вызова), вам может быть лучше использовать getContext(), самостоятельно проверяя его возвращаемое значение и продолжая использовать его только в том случае, если оно не равно нулю.

Библиотека Android для планирования и запуска асинхронных задач в фоновом режиме. Она предоставляет простой способ выполнения задач даже тогда, когда приложение уже закрыто, а также автоматически управляет запуском задач, чтобы минимизировать потребление ресурсов устройства.

class MyWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
    override fun doWork(): Result {
        // Здесь выполняется асинхронная задача, например, загрузка файла
        return Result.success()
    }
}

val myWorkRequest = OneTimeWorkRequestBuilder<MyWorker>().build()
WorkManager.getInstance(context)
    .enqueue(myWorkRequest)
    
// пример для нескольких
val workA = OneTimeWorkRequestBuilder<MyWorkerA>().build()
val workB = OneTimeWorkRequestBuilder<MyWorkerB>().build()
val workC = OneTimeWorkRequestBuilder<MyWorkerC>().build()
val workD = OneTimeWorkRequestBuilder<MyWorkerD>().build()
WorkManager.getInstance(context)
    .beginWith(workA, workB, workC)
    .combine()
    .then(workD)
    .enqueue()

enqueue():
помещает задачу в очередь на выполнение. Она будет выполнена, как только устройство будет готово к этому (например, при наличии интернет-соединения, достаточной зарядке аккумулятора и т.д.).

beginWith():
используется для запуска нескольких задач в заданном порядке.

combine():
используется для запуска нескольких задач параллельно и ожидания их завершения перед выполнением следующей задачи.

WorkManager и LiveData:
позволяет получать уведомления о состоянии выполнения задач в WorkManager. Например, вы можете создать LiveData объект и использовать его для отображения прогресса выполнения задачи.

class MyViewModel(application: Application) : AndroidViewModel(application) {
  
    private val workManager = WorkManager.getInstance(application)
    private val workInfoLiveData = MutableLiveData<WorkInfo>()
    
    fun startWork() {
        val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
            .build()
        workManager.enqueue(workRequest)
        workManager.getWorkInfoByIdLiveData(workRequest.id)
            .observeForever { workInfo ->
                workInfo?.let {
                    workInfoLiveData.postValue(workInfo)
                }
            }
    }
    
    fun getWorkInfoLiveData(): LiveData<WorkInfo> {
        return workInfoLiveData
    }
    
}

class MyActivity : AppCompatActivity() {
  
    private lateinit var viewModel: MyViewModel
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
        viewModel.getWorkInfoLiveData().observe(this, { workInfo ->
            if (workInfo != null && workInfo.state == WorkInfo.State.RUNNING) {
                // Отображаем прогресс выполнения
            } else if (workInfo != null && workInfo.state.isFinished) {
                // Задача завершена
            }
        })
        viewModel.startWork()
    }
    
}

WorkManager и RxJava:
можно использовать библиотеку RxWorkManager, которая предоставляет удобный способ создания Observable потоков из WorkRequest объектов. Вот пример использования RxWorkManager для запуска OneTimeWorkRequest и получения результата в виде Single:

val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
    .build()
    
val workObservable = RxWorkManager
    .getInstance(application)
    .getWorkInfoByIdObservable(workRequest.id)
    .flatMap { workInfo ->
        if (workInfo.state == WorkInfo.State.SUCCEEDED) {
            val outputData = workInfo.outputData
            Single.just(outputData.getString("result"))
        } else {
            Single.error(Throwable("Work failed"))
        }
    }
    
workObservable.subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe({ result ->
            // Обработка успешного результата
        },{ error ->
            // Обработка ошибки
        }
    )
    
WorkManager.getInstance(application).enqueue(workRequest)

WorkManager и корутины:
можно использовать библиотеку kotlinx-coroutines-play-services, которая предоставляет удобный способ запуска и отслеживания задач в WorkManager с помощью корутин.

val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
    .build()
val workInfoDeferred = WorkManager.getInstance(context)
    .getWorkInfoById(workRequest.id)
    .asDeferred()
    
// Запускаем задачу в фоновом потоке
GlobalScope.launch(Dispatchers.IO) {
    WorkManager.getInstance(context).enqueue(workRequest)
}

// Ожидаем результат выполнения задачи в основном потоке
val workInfo = workInfoDeferred.await()
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
    val outputData = workInfo.outputData
    val result = outputData.getString("result")
    // Обработка успешного результата
} else {
    // Обработка ошибки
}

Нет, байт-код JVM интерпретируется в байт-код ART (Android Runtime), и затем выполняется. До версии 5.0 вместо ART был DRT (Dalvik)

Ядро Linux:
Сердце Android, в котором реализуется безопасность межпроцессного взаимодействия и низкоуровневая работа с памятью.

Hardware Abstraction Layer (HAL):
Интерфейсы для работы с железом. Драйвер для USB, Bluetooth, программный интерфейс OpenGL. Уровень, который дает платформо-независимость Android.

Android Runtime и нативные библиотеки:
То что выполняет пользовательский код: компиляторы, сборщик мусора, интерпретатор байткода.

Android Framework:
Java API, через которое пользовательская программа взаимодействует с системой. Обеспечение жизненного цикла системных компонентов.

Android Applications:
Непосредственно приложения, как пользовательские, так и системные (календарь, камера, и т.д.).

представляет собой класс, используемый для передачи данных между компонентами приложения, такими как активности (Activity), фрагменты (Fragment) и службы (Service). Bundle действует как контейнер для пары «ключ-значение», где ключи являются строками, а значения могут быть разного типа, включая примитивные типы, объекты, реализующие интерфейс Serializable или Parcelable.

Основные сценарии использования Bundle:
Передача данных между активностями
Сохранение состояния активности
Передача данных между фрагментами
Передача данных в службу

Bundle хранятся в системном классе ActivityManagerService. Для каждой запущенной активити создается инстанс класса ActivityRecord. Этот класс имеет поле icicle типа Bundle. Именно в это поле сохраняется состояние после вызова onSaveInstanceState().

При остановке активити Bundle отправляется в системный процесс через Bindler IPC. Далее на классе ActivityManagerService вызывается метод activityStopped(), который принимает объект Bundle. Этот метод находит ActivityRecord, соответствующий остановленной активити и записывает полученный Bundle в поле icicle класса ActivityRecord.

Передача данных между активностями:

import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

  
class MainActivity : AppCompatActivity() {
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener {
            val intent = Intent(this, SecondActivity::class.java)
            val bundle = Bundle().apply {
                putString("KEY_MESSAGE", "Hello from MainActivity")
                putInt("KEY_NUMBER", 123)
            }
            intent.putExtras(bundle)
            startActivity(intent)
        }
    }
    
}
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_second.*

class SecondActivity : AppCompatActivity() {
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        val bundle = intent.extras
        val message = bundle?.getString("KEY_MESSAGE")
        val number = bundle?.getInt("KEY_NUMBER")
        textView.text = "$message, Number: $number"
    }
    
}

Сохранение состояния активности:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity


class MainActivity : AppCompatActivity() {
  
    private var counter: Int = 0
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Восстановление состояния
        savedInstanceState?.let {
            counter = it.getInt("KEY_COUNTER", 0)
        }
    }
    
    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        // Сохранение состояния
        outState.putInt("KEY_COUNTER", counter)
    }
    
}

Передача данных между фрагментами:

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.fragment_first.*

  
class FirstFragment : Fragment() {
  
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        button.setOnClickListener {
            val bundle = Bundle().apply {
                putString("KEY_MESSAGE", "Hello from FirstFragment")
            }
            val secondFragment = SecondFragment().apply {
                arguments = bundle
            }
            fragmentManager?.beginTransaction()
                ?.replace(R.id.fragment_container, secondFragment)
                ?.addToBackStack(null)
                ?.commit()
        }
    }
    
}
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.fragment_second.*

  
class SecondFragment : Fragment() {
  
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_second, container, false)
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val message = arguments?.getString("KEY_MESSAGE")
        textView.text = message
    }
    
}

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

Основные виды Intent:

Explicit Intent (Явные намерения):
Используются для запуска определенного компонента внутри вашего приложения. Вы явно указываете класс компонента, который должен быть запущен.

// MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val button = findViewById<Button>(R.id.button)
        button.setOnClickListener {
            val intent = Intent(this, SecondActivity::class.java)
            startActivity(intent)
        }
    }
}


// SecondActivity.kt
class SecondActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
    }
}

Передача данных:

// MainActivity.kt
val intent = Intent(this, SecondActivity::class.java).apply {
    putExtra("EXTRA_MESSAGE", "Hello, SecondActivity!")
}
startActivity(intent)


// SecondActivity.kt
class SecondActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        val message = intent.getStringExtra("EXTRA_MESSAGE")
        findViewById<TextView>(R.id.textView).text = message
    }
}

Implicit Intent (Неявные намерения):
Используются для выполнения действия без указания конкретного компонента. Вместо этого вы указываете желаемое действие и систему, чтобы найти подходящий компонент для выполнения этого действия.

Запуск веб-браузера для открытия URL:

val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("https://www.example.com")
startActivity(intent)

Отправка электронной почты:

val intent = Intent(Intent.ACTION_SENDTO).apply {
    data = Uri.parse("mailto:") // Только для email приложений
    putExtra(Intent.EXTRA_EMAIL, arrayOf("[email protected]"))
    putExtra(Intent.EXTRA_SUBJECT, "Subject of Email")
}
if (intent.resolveActivity(packageManager) != null) {
    startActivity(intent)
}

Основные атрибуты:

Action (Действие):
Строка, определяющая действие, которое должно быть выполнено, например, ACTION_VIEW, ACTION_SEND, ACTION_MAIN и т.д.

Data (Данные):
URI, указывающий данные, на которых должно быть выполнено действие.

Category (Категория):
Дополнительные характеристики для фильтрации компонентов, которые могут обрабатывать намерения.

Extras (Дополнительные данные):
Дополнительные данные, которые могут быть переданы компоненту, например, putExtra(«key», value).

Component (Компонент):
Имя класса компонента, который должен быть запущен (для явных намерений).

В некоторых случаях вам может понадобиться передать одноразовое значение между двумя фрагментами или между фрагментом и его активностью хоста. Например, у вас может быть фрагмент, который считывает QR-коды, передавая данные обратно предыдущему фрагменту. Начиная с фрагмента 1.3.0-alpha04 каждый FragmentManager реализует FragmentResultOwner. Это означает, что a FragmentManagerможет действовать как центральное хранилище результатов фрагментов. Это изменение позволяет компонентам взаимодействовать друг с другом, устанавливая результаты фрагментов и прослушивая эти результаты, не требуя, чтобы эти компоненты имели прямые ссылки друг на друга.

Чтобы передать данные обратно к фрагменту A из фрагмента B, сначала установите слушатель результатов на фрагмент A, который получает результат. Вызов setFragmentResultListener() фрагмента A FragmentManager, как показано в следующем примере:

// Отправляем результат в другой фрагмента
val someData = "123"
SomeFragmentManager?.setFragmentResult(
  BUNDLE_KEY,
  bundleOf(STRING_KEY to someData)
)
// получаем результат в другом фрагменте
SomeFragmentManager?.setFragmentResultListener(
 BUNDLE_KEY, viewLifecycleOwner
) { _, bundle ->
   bundle.getString(STRING_KEY)?.let(viewModel::setString)

В операционной системе Android процесс Zygote (с англ. “зигота”) — это специальный процесс, который запускается при старте системы и занимается предварительной инициализацией, компиляцией и кэшированием кода приложений, чтобы ускорить их запуск.

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

Zygote также предоставляет системные службы и ресурсы, которые могут использоваться в приложениях, такие как системные виджеты, службы местоположения, сертификаты безопасности и т.д.

Zygote играет важную роль в работе Android, так как позволяет ускорить запуск приложений и обеспечивает более эффективное использование ресурсов системы.

class CustomView @JvmOverloads constructor(
  context: Context, 
  attrs: AttributeSet? = null, 
  defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
  
  init {
    setupAttributes(attrs)
  }
  
  private fun setupAttributes(attrs: AttributeSet?) {
    context.withStyledAttributes(attrs, R.styleable.CustomView) {
      getBoolean(R.styleable.CustomView_variable_name, true).let { variable ->       
      }
    }
  }
  
}
// в attrs.xml пишем
<declare-styleable name="CustomView">
  <attr format="boolean" name="variable_name" />
</declare-styleable>

// теперь в макете фрагмента можем добавить
<com._.CustomView
  android:id="@+id/customView"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  app:variable_name="true"
  />

// теперь на предпросмотре и в приложении можно будет видеть 
// измненения, связанные с этой переменной
// если это нужно только джля предпросмотра, 
// используем tools:variable_name="true" вместо app:variable_name="true"

Основные типы переменных:

<attr format="boolean" name="name">
<attr format="string"name="name">
<attr format="integer"name="name">
<attr format="reference"name="name">
<attr format="color"name="name">
<attr format="reference|color"name="name">
<attr format="dimension"name="name">
<attr format="enum" name="gravity">
    <enum name="start" value="0" />
    <enum name="end" value="1" />
</attr>

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

Spannable:
Интерфейс, расширяющий CharSequence и позволяющий изменять стили текста.

SpannableString:
Класс, реализующий Spannable и используемый для применения стилей к строкам.

SpannableStringBuilder:
Класс, который позволяет создавать и изменять текст с различными стилями.

Типы Spans:

StyleSpan:
Применяет стиль (например, жирный или курсив).

ForegroundColorSpan:
Изменяет цвет текста.

BackgroundColorSpan:
Изменяет цвет фона текста.

UnderlineSpan:
Добавляет подчеркивание к тексту.

StrikethroughSpan:
Добавляет зачеркнутый текст.

ClickableSpan:
Делает текст кликабельным и позволяет выполнять действия при нажатии.

ImageSpan:
Вставляет изображение в текст.

URLSpan:
Создает гиперссылку.

import android.graphics.Color
import android.os.Bundle
import android.text.Spannable
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.*
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity


class MainActivity : AppCompatActivity() {
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val textView = findViewById<TextView>(R.id.textView)
        val spannableString = SpannableString("Hello, World! This is a spannable string.")
        // Применение жирного стиля к тексту "Hello"
        val boldSpan = StyleSpan(android.graphics.Typeface.BOLD)
        spannableString.setSpan(boldSpan, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        // Применение цвета текста к "World"
        val colorSpan = ForegroundColorSpan(Color.RED)
        spannableString.setSpan(colorSpan, 7, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        // Применение подчеркивания к "This"
        val underlineSpan = UnderlineSpan()
        spannableString.setSpan(underlineSpan, 14, 18, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        // Применение зачеркнутого текста к "spannable"
        val strikethroughSpan = StrikethroughSpan()
        spannableString.setSpan(strikethroughSpan, 23, 33, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        // Применение кликабельного текста к "string"
        val clickableSpan = object : ClickableSpan() {
            override fun onClick(widget: View) {
                // Действие при клике на текст
            }
            override fun updateDrawState(ds: TextPaint) {
                super.updateDrawState(ds)
                ds.isUnderlineText = false // Убираем подчеркивание
            }
        }
        spannableString.setSpan(clickableSpan, 34, 40, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        textView.text = spannableString
        textView.movementMethod = android.text.method.LinkMovementMethod.getInstance()
    }
    
}

Использование SpannableStringBuilder:

val spannableStringBuilder = SpannableStringBuilder()
spannableStringBuilder.append("Bold Text")
spannableStringBuilder.setSpan(StyleSpan(android.graphics.Typeface.BOLD), 0, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
spannableStringBuilder.append(" and ")
spannableStringBuilder.append("Red Text")
spannableStringBuilder.setSpan(ForegroundColorSpan(Color.RED), 14, 22, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = spannableStringBuilder

Build Type:
используется, чтобы задать настройки сборки, например будет ли использоваться proguard и каким сертификатом подписывается apk. Самые часто используемые типы сборки: debug и release. Тип сборки задается параметром buildTypes в gradle-файле.

buildTypes {
   debug {
       applicationIdSuffix ".debug"
       debuggable true
       minifyEnabled false
   }
   release {
       minifyEnabled false
       proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
   }
}

Flavor:
используется для определения сборок приложения с разной функциональностью. Это могут быть платные фичи, различные таргет API, разные ресурсы. Задается параметром productFlavors в gradle-файле.

productFlavors {
  	free {
      dimension "version"
       applicationId "ru.itsobes.flavors.free"
      resValue "string", "flavored_app_name", "Free It Sobes App"
    }
	paid {
       dimension "version"
       applicationId "ru.itsobes.flavors.paid"
       resValue "string", "flavored_app_name", "Paid It Sobes App"
    }
}

Build Variant:
это комбинация build type и flavor. Для описанных выше build type и flavor создаются четыре build variant: freeDebug, freeRelease, paidDebug, paidRelease.

SharedPreferences:
сохраняет данные в виде пар ключей и значений в папки приложения. В нем есть Editor для редактирования, в котором commit(), который возвращает результаты сохранения и apply(), который не возвращает результаты.
У SharedPreferences есть OnSharedPreferenceChangeListener, который позволяет отслеживать изменения по ключу

EncryptedSharedPreferences:
это зашифрованная реализация sharedPreferences

DataStore:
это замена SharedPreferences, которая устраняет большинство этих недостатков. DataStore включает в себя полностью асинхронный API, использующий Kotlin Coroutines и Flow.

Механизм в Android, который генерирует привязки к XML-макетам во время компиляции. Это позволяет вам безопасно и эффективно взаимодействовать с представлениями в коде, устраняя необходимость использования метода findViewById и снижая вероятность ошибок типа NullPointerException.

Основные преимущества ViewBinding:

Безопасность типов:
Код сгенерирован ViewBinding гарантирует, что вы обращаетесь только к существующим представлениям.

Избегание NullPointerException:
ViewBinding генерирует классы, которые безопасны для null, что уменьшает вероятность получения NullPointerException.

Упрощение кода:
Устраняет необходимость явного вызова findViewById, что делает код чище и проще.

android {
    viewBinding {
        enabled = true
    }
}

Использование ViewBinding в Activity:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello, ViewBinding!" />
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click me" />
</LinearLayout>
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.viewbindingexample.databinding.ActivityMainBinding


class MainActivity : AppCompatActivity() {
  
    private lateinit var binding: ActivityMainBinding
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.textView.text = "Updated with ViewBinding!"
        binding.button.setOnClickListener {
            binding.textView.text = "Button Clicked!"
        }
    }
    
}

Использование ViewBinding во фрагменте:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello, ViewBinding in Fragment!" />
</FrameLayout>
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.viewbindingexample.databinding.FragmentExampleBinding


class ExampleFragment : Fragment() {
  
    private var _binding: FragmentExampleBinding? = null
    private val binding get() = _binding!!
    
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentExampleBinding.inflate(inflater, container, false)
        return binding.root
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.textView.text = "Updated with ViewBinding in Fragment!"
    }
    
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
    
}

Включение ViewBinding:
В build.gradle включается поддержка ViewBinding, добавляя блок viewBinding { enabled = true }.

Создание экземпляра класса привязки:
В MainActivity и ExampleFragment создается экземпляр класса привязки, сгенерированного на основе имени XML-макета (например, ActivityMainBinding для activity_main.xml).

Инициализация привязки:
В onCreate для активности и onCreateView для фрагмента инициализируется объект привязки, вызывая метод inflate.

Доступ к представлениям:
Вместо использования findViewById, элементы интерфейса доступны через объект привязки, например, binding.textView и binding.button.

Управление жизненным циклом во фрагментах:
В onDestroyView фрагмента обнуляется объект привязки _binding для предотвращения утечек памяти.

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

Как работает RecyclerView:

Повторное использование видов:
Когда элемент списка выходит за пределы экрана, его представление помещается в пул для повторного использования. Это улучшает производительность и снижает нагрузку на память.

Менеджер компоновки (LayoutManager):
Определяет, как элементы будут размещаться внутри RecyclerView. В примере используется LinearLayoutManager, но также доступны GridLayoutManager и StaggeredGridLayoutManager.

Адаптер:
Адаптер обеспечивает связь данных и представлений. Он создает новые виды по мере необходимости и заполняет их данными.

Основные компоненты RecyclerView:

Adapter:
предоставляет данные для элементов списка и создает новые виды элементов (ViewHolder).

ViewHolder:
удерживает представления элементов списка и обеспечивает их повторное использование.

LayoutManager:
отвечает за измерение и позиционирование элементов внутри RecyclerView.

Основные методы RecyclerView.Adapter

onCreateViewHolder:
Создает новые виды элементов списка (ViewHolder).

onBindViewHolder:
Заполняет вид данными из набора данных.

getItemCount:
Возвращает общее количество элементов в наборе данных.

dependencies {
    implementation 'androidx.recyclerview:recyclerview:1.2.1'
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp">
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Item"
        android:textSize="18sp" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView


class MyAdapter(private val itemList: List<String>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
  
    class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val textView: TextView = itemView.findViewById(R.id.textView)
    }
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val itemView = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_view, parent, false)
        return MyViewHolder(itemView)
    }
    
    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val currentItem = itemList[position]
        holder.textView.text = currentItem
    }
    
    override fun getItemCount() = itemList.size
  
}
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView


class MainActivity : AppCompatActivity() {
  
    private lateinit var recyclerView: RecyclerView
    private lateinit var adapter: MyAdapter
    private lateinit var itemList: List<String>
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        recyclerView = findViewById(R.id.recyclerView)
        // Инициализация данных
        itemList = listOf("Item 1", "Item 2", "Item 3", "Item 4", "Item 5")
        // Настройка адаптера и layout manager
        adapter = MyAdapter(itemList)
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(this)
    }
    
}

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

Custom Drawing:
это процесс рисования на холсте с помощью методов класса Canvas. Этот подход позволяет рисовать на экране различные элементы, например, линии, прямоугольники, круги, текст, изображения и т.д. Custom Drawing может использоваться, когда необходимо создать динамические элементы интерфейса, которые не могут быть достигнуты с помощью стандартных элементов пользовательского интерфейса.

Deep Link:
это ссылка на конкретный контент внутри мобильного приложения. Это позволяет пользователям переходить напрямую к нужному экрану приложения, минуя дополнительные шаги, такие как поиск в приложении. Приложение, которое поддерживает Deep Link, может обрабатывать определенные URL-адреса и запускать соответствующие экраны приложения, если они доступны на устройстве пользователя.

App Link:
это более продвинутая версия Deep Link, которая позволяет приложению зарегистрироваться для обработки определенных URL-адресов в качестве своих собственных. Это означает, что пользователь может перейти к контенту приложения через веб-сайт, и система автоматически откроет соответствующее приложение, если оно установлено на устройстве пользователя. Приложение также может предоставлять информацию о себе для веб-сайта, такую как иконки и заголовки, что делает процесс более интуитивным и привлекательным для пользователей.

NavGraph:
список фрагментов, который мы будем создавать и наполнять. NavController сможет показывать фрагменты только из этого списка

NavHostFragment:
контейнер. Внутри него NavController будет отображать фрагменты.

NavController:
объект, управляющий навигацией приложения в NavHost. Он координирует смену контента пунктов назначения в NavHost в процессе перемещения пользователя по приложению.

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_main"
    app:startDestination="@id/mainFragment">

  <fragment
      android:id="@+id/mainFragment"
      android:name="com._.Main"
      android:label="mainFragment"
      tools:layout="@layout/fragment_main">
    <action
        android:id="@+id/action_mainFragment_to_settingsFragment"
        app:destination="@id/settingsFragment"
        app:popUpTo="@id/settingsFragment"
        app:popUpToInclusive="true"/>
    <action
        android:id="@+id/action_mainFragment_to_settingsBottomSheetFragment"
        app:destination="@id/settingsBottomSheetFragment" />
  </fragment>

  <fragment
      android:id="@+id/settingsFragment"
      android:name="com._.settingsFragment"
      android:label="settingsFragment"
      tools:layout="@layout/fragment_settings">
  </fragment>

  <dialog
      android:id="@+id/settingsBottomSheetFragment"
      android:name="com._.SettingsBottomSheetFragment"
      android:label="settingsBottomSheetFragment"
      tools:layout="@layout/bottom_sheet_settings" />

</navigation>

URI (Uniform Resource Identifier) для доступа к данным внутри приложений Android. Он используется для получения доступа к различным типам данных, таким как изображения, видео, звук, контакты, календари и т.д.
Content Uri обычно имеют формат “content://authority/path/id”, где “authority” — это идентификатор поставщика контента, “path” — это путь к конкретному ресурсу, а “id” — это уникальный идентификатор ресурса.
Например, для получения доступа к фотографиям, хранящимся в галерее устройства, можно использовать следующий Content Uri: “content://media/external/images/media”. Для получения доступа к определенному изображению можно добавить идентификатор изображения в конце Uri: “content://media/external/images/media/123”.
Content Uri используется для обмена данными между различными приложениями и компонентами Android, а также для выполнения операций чтения и записи данных, таких как добавление, обновление и удаление записей.

В папке res создается папка anim с ресурсами анимации
В файле res/navigation/nav_graph.xml на вкладке дизайна выделяем стрелку перехода между экранами и в панели атрибутов справа открываем раздел Animations.

У перехода есть четыре свойства, которые можно анимировать:
enterAnim: вход в пункт назначения
exitAnim: выход из пункта назначения
popEnterAnim: вход в пункт назначения с помощью pop action
popExitAnim: выход из пункта назначения с помощью pop action
View можно анимировать с помощью Transition Framework

PNG- растровый формат, при сильном увеличении будут видны пиксели, SVG- векторный и не зависит от разрешения экрана

onSaveInstanceState():
вызывается перед уничтожением активности, например, при повороте экрана, сворачивании приложения или смене конфигурации устройства. Этот метод используется для сохранения состояния активности, которое может потеряться при уничтожении и восстановлении активности. Также вызывается перед тем, как активность будет уничтожена системой в случае нехватки памяти. В этом случае метод используется для сохранения состояния активности, которое может быть восстановлено при необходимости.

onRestoreInstanceState():
вызывается после того, как активность была восстановлена из состояния, сохраненного в onSaveInstanceState(). Этот метод используется для восстановления состояния активности, сохраненного в onSaveInstanceState(). Может быть вызван не только после поворота экрана или изменения конфигурации устройства, но также в других случаях, когда активность была уничтожена и пересоздана системой, например, при восстановлении после аварийной остановки приложения.

dpi:
плотность экрана, количество пикселей на дюйм.

dp, sp:
абстрактные независимые от плотности пиксели для размеров элементов и текста. Ранее мы писали о соотношении между dp и px.

dip:
не упоминаемый в документации, но понимаемый компилятором синоним для dp.

px:
размер, выраженный в физическом количестве пикселей экрана.

mm, in:
физический размер на экране, в милиметрах и дюймах (inches) соответственно.

pt:
типографский пункт (point), также физический размер, равный 1/72 дюйма.

Handler:
позволяет выполнять отложенные во времени задачи. Не является планировщиком задач в чистом виде, т.к. работает на уровне процесса приложения, а не операционной системы. Если приложение остановлено, код, зашедуленный через Handler не будет выполнен. Handler позволяет отправлять сообщения в другие потоки с задержкой или без, а также обрабатывать полученные сообщения. Handler всегда связан с Looper, который в свою очередь связан с каким-либо потоком. При создании Handler в конструктор можно передать объект Looper. Если используется дефолтный конструктор, то Handler создается на текущем потоке. Если с потоком не связан Looper, то при создании Handler бросается RuntimeException.

AlarmManager:
запускает запланированные операции даже если приложение остановлено. В качестве операций используются PendingIntents. AlarmManager доступен с API v1, но по умолчанию не работает в Doze Mode. Для работы в Doze Mode используется метод setAndAllowWhileIdle(). Этот метод доступен с API v23.

JobScheduler:
позволяет работать в Doze Mode и доступен с API v21.
Система группирует задачи, запланированные через JobScheduler. Когда появляется окно в Doze Mode, выполняется сразу несколько задач. Этот подход бережет батарейку устройства.

WorkManager:
библиотека из Android Jetpack. WorkManager работает начиная с API v14. Google рекомендует использовать WorkManager вместо решений, разработанных ранее. Под капотом WorkManager использует JobScheduler на устройствах с API v23+. На версиях API 14–22 используется GCMNetworkManager или AlarmManager.

LinearLayout:
это самый простой тип Layout, который ориентируется на горизонтальное или вертикальное расположение дочерних элементов. Быстро работает на всех версиях Android.

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

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

ConstraintLayout:
это тип Layout, который позволяет создавать сложные иерархии View с использованием ограничений. ConstraintLayout может быть эффективным, если он настроен правильно, и может даже превосходить производительность RelativeLayout.

FrameLayout:
это простой тип Layout, который может содержать только один дочерний элемент. Он быстро работает, но не подходит для размещения нескольких элементов.

TableLayout:
предназначен для размещения элементов в виде таблицы. Он может быть удобным для экранов с большим количеством данных или для отображения списка.

GridLayout:
позволяет размещать элементы в виде сетки. Он особенно полезен для экранов с большим количеством данных, таких как экраны списка или таблиц.

ScrollView:
используется для создания прокручивающегося контента. Этот тип layout-а может быть полезен для экранов с большим количеством текста или изображений.

Под main activity понимается активити, у которой intent-filter содержит ACTION_MAIN и CATEGORY_LAUNCHER.
В AndroidManifest можно добавить несколько main activity.
Для каждой активити появится иконка в меню приложений.
По-умолчанию будет использоваться иконка и имя приложения, заданные в атрибутах icon и label элемента application в манифесте. Эти атрибуты можно переопределить в элементе activity.
Этот механизм может быть полезен для библиотек, которые используются в debug/testing билдах. Так библиотека leakcanary добавляет иконку для запуска своей активити из меню приложений.

Сгенерированные для классов данных компонентные функции позволяют использовать их в мульти-декларациях.

val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") 
// выводит "Jane, 35 years of age"

Foreground Service:
сервис, который выполняется в первом плане и предоставляет пользователю заметную функциональность. Фоновый сервис может быть превращен в foreground сервис с помощью вызова метода startForeground().

Background Service:
сервис, который выполняется в фоне и не предоставляет пользователю заметной функциональности. В отличие от foreground сервиса, background сервис может быть уничтожен системой в любой момент, когда системе потребуется освободить память.

Bound Service:
сервис, который предоставляет связь между приложением и другой частью системы, такой как другое приложение или служба. Bound сервис может взаимодействовать с клиентом, предоставляя ему объект IBinder через метод onBind(). Клиенты могут использовать этот объект для вызова методов, которые определены в сервисе. Когда все клиенты отключаются от сервиса, он уничтожается.

IntentService:
это подкласс Service, который используется для выполнения задач в фоновом режиме.
IntentService автоматически создает новый поток для обработки каждого интента, отправленного в службу. Когда интент обработан, служба автоматически завершается. Если во время выполнения службы приходит новый интент, то он добавляется в очередь и обрабатывается по очереди.
IntentService также обеспечивает синхронизацию, блокируя новые интенты до тех пор, пока не будет завершена обработка предыдущих.
Основное использование IntentService — это выполнение длительных операций, таких как загрузка данных из сети, обработка изображений, сохранение данных в базу данных и т.д. Все эти операции можно выполнять в фоновом режиме, чтобы не блокировать основной поток пользовательского интерфейса.

JobIntentService:
это устаревший класс в Android, который предназначен для выполнения фоновых задач, которые можно выполнить в фоновом режиме с помощью службы. Он был представлен в Android Support Library для обеспечения обратной совместимости. JobIntentService работает подобно IntentService, но имеет дополнительную функциональность для обработки задач, которые необходимо выполнить в фоновом режиме, и для обработки этих задач в фоновом режиме даже после того, как приложение было остановлено. В отличие от обычного IntentService, JobIntentService автоматически управляет жизненным циклом службы и гарантирует, что служба не будет остановлена, пока все задачи не будут выполнены.
Начиная с Android 12, рекомендуется использовать WorkManager для запуска фоновых задач вместо JobIntentService.

Пример использования сервиса:
Создайте класс для сервиса, наследующийся от Service. Например, создайте файл MyService.kt со следующим содержимым:

class MyService : Service() {

    private val binder = MyServiceBinder()

    override fun onCreate() {
        // Выполняется при создании сервиса
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // Выполняется при запуске сервиса
        return START_STICKY
    }

    override fun onDestroy() {
        // Выполняется при завершении сервиса
    }

    override fun onBind(intent: Intent?): IBinder? {
        // метод, который должен быть переопределен в классе Service, 
        // чтобы предоставить клиенту (обычно активности) возможность связаться с сервисом через интерфейс IBinder.
        // Этот метод должен вернуть объект IBinder, 
        // который клиент может использовать для взаимодействия с сервисом. 
        // Если сервис не предоставляет возможность связи с клиентом, 
        // этот метод может просто возвращать null.
        return binder
    }

inner class MyServiceBinder : Binder() {
        fun getService(): MyService {
            return this@MyService
        }
    }
}

Добавьте сервис в файл манифеста приложения, указав имя класса сервиса и разрешения, если это необходимо. Например, добавьте следующий код в файл AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplication">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">

        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="false" />

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Запуск и остановка

val serviceIntent = Intent(this, MyService::class.java)
startService(serviceIntent)

val serviceIntent = Intent(this, MyService::class.java)
stopService(serviceIntent)

SensorManager:
точка входа для работы с сенсорами и listener-ами их событий. Системный сервис, получаемый по имени Context.SENSOR_SERVICE;

Sensor:
представляет отдельно взятый сенсор. Дает различную метаинформацию (энергопотребление, точность, производителя, и т.д.);

SensorEventListener:
интерфейс для реализации обработчиков событий, приходящих из сенсоров. В нём реализуется логика обработки входящих данных;

SensorEvent:
отдельное событие из сенсора: данные и точность их измерения.

@Entity(tableName = “users”):
data class

@Entity(primaryKeys = [“firstName”, “lastName”]):
используется composite primary key

@Entity(ignoredColumns = [“picture”]):
игнорировать

@PrimaryKey(autoGenerate = true):
первичный ключ в data class

@ColumnInfo(name = “first_name”):
название колонки, если оно должно отличаться от названия переменной

@Ignore:
не использовать это поле или этот конструктор

@Dao:
абстрактный класс или интерфейс для работы с таблицей

@Transaction:
выполнять как одну транзакцию, атомарно

@Database(entities = [User::class], version = 1):
абстрактный класс, который екстендит RoomDatabase

@Database(entities = [User::class], version = 1):
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}
val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "database-name").build()

@TypeConverters(DateConverter.class):

class Converters {
  @TypeConverter
  fun fromTimestamp(value: Long?): Date? {
    return value?.let { Date(it) }
  }
@TypeConverter
  fun dateToTimestamp(date: Date?): Long? {
    return date?.time?.toLong()
  }
}
@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
  abstract fun userDao(): UserDao
}
val db = Room.databaseBuilder(...)
  .addTypeConverter(exampleConverterInstance)
  .build()

@Query(“SELECT * FROM Users”):
sql запрос

@Insert(onConflict = OnConflictStrategy.REPLACE):
вставка в таблицу

@Delete:
удаление из таблицы

@Update:
обновление

@DatabaseView:
инкапсулирует запрос в класс

@Fts3 или @Fts4:
в Room 2.1.0 использовать полнотекстовый поиск FTS

@AutoValue:
https://github.com/google/auto/blob/master/value/userguide/index.md

@MapInfo(keyColumn = “userName”, valueColumn = “bookName”):
возвращает сопоставление между таблицами

@Embedded(prefix = “address”):
взять поля из помеченного класса и считать полями таблицы, prefix нужно если совпадают имена

@Relation(parentColumn = “userId”, entityColumn = “userOwnerId”):
будет искать где parentColumn равен entityColumn, также может иметь свойство associateBy = Junction(PlaylistSongCrossRef::class) где

@Entity(primaryKeys = ["playlistId", "songId"])
data class PlaylistSongCrossRef(
    val playlistId: Long,
    val songId: Long
)

Зависимость один к одному:

@Entity
data class User(
    @PrimaryKey val userId: Long,
    val name: String,
    val age: Int
)
@Entity
data class Library(
    @PrimaryKey val libraryId: Long,
    val userOwnerId: Long
)
data class UserAndLibrary(
    @Embedded val user: User,
    @Relation(parentColumn = "userId", entityColumn = "userOwnerId")
    val library: Library
)
@Transaction
@Query("SELECT * FROM User")
fun getUsersAndLibraries(): List<UserAndLibrary>

Зависимость один к многим:

@Entity
data class User(
    @PrimaryKey val userId: Long,
    val name: String,
    val age: Int
)
@Entity
data class Playlist(
    @PrimaryKey val playlistId: Long,
    val userCreatorId: Long,
    val playlistName: String
)
data class UserWithPlaylists(
    @Embedded val user: User,
    @Relation(parentColumn = "userId", entityColumn = "userCreatorId")
    val playlists: List<Playlist>
)
@Transaction
@Query("SELECT * FROM User")
fun getUsersWithPlaylists(): List<UserWithPlaylists>

Зависимость многие к многим:

@Entity
data class Playlist(
    @PrimaryKey val playlistId: Long,
    val playlistName: String
)
@Entity
data class Song(
    @PrimaryKey val songId: Long,
    val songName: String,
    val artist: String
)
@Entity(primaryKeys = ["playlistId", "songId"])
data class PlaylistSongCrossRef(
    val playlistId: Long,
    val songId: Long
)
data class PlaylistWithSongs(
    @Embedded val playlist: Playlist,
    @Relation(parentColumn = "playlistId", entityColumn = "songId", associateBy = Junction(PlaylistSongCrossRef::class))
    val songs: List<Song>
)
data class SongWithPlaylists(
    @Embedded val song: Song,
    @Relation(parentColumn = "songId", entityColumn = "playlistId", associateBy = Junction(PlaylistSongCrossRef::class))
    val playlists: List<Playlist>
)
@Transaction
@Query("SELECT * FROM Playlist")
fun getPlaylistsWithSongs(): List<PlaylistWithSongs>
@Transaction
@Query("SELECT * FROM Song")
fun getSongsWithPlaylists(): List<SongWithPlaylists>

При помощи Join можно можно объединять таблицы в Room способом, альтернативным Embedded и Relation, запросы при этом сложней писать по SQL синтаксису, но работает намного быстрей, чем автоматическое объединение через Embedded и Relation

Асинхронные запросы:
coroutines: suspend fun loadUserById(id: Int): User
RX: public Single loadUserById(int id);
LiveData: public ListenableFuture loadUserById(int id);

Подписка:
coroutines: fun loadUserById(id: Int): Flow
RX: public Flowable loadUserById(int id);
LiveData: public LiveData loadUserById(int id);

Предварительное заполнение:

Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
    .createFromAsset("database/myapp.db")
    .build()
// или
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
    .createFromFile(File("mypath"))
    .build()

Миграции:

Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
    .createFromAsset("database/myapp.db")
    .fallbackToDestructiveMigration()
    .build()
// или
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
    .createFromAsset("database/myapp.db")
    .addMigrations(MIGRATION_1_2)
    .build()
    val MIGRATION_1_2 = object : Migration(1, 2) {
      override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, " +
          "PRIMARY KEY(`id`))")
      }
    }
// или
@Database(version = 2, entities = [User::class], autoMigrations = [AutoMigration (from = 1, to = 2)])

Copyright: Roman Kryvolapov