Модификации могут означать разные вещи в зависимости от контекста. В Android-разработке чаще всего речь идёт о следующих типах модификаций:
Модификация данных (изменение состояния)
Модификация UI (Compose Modifiers)
Модификация кода (рефакторинг, оптимизация)
Модификация системы (кастомные прошивки, рут-изменения)
Это изменение переменных, объектов и структур данных во время работы приложения.
class ViewModel : ViewModel() {
private val _state = MutableStateFlow("Привет, мир!")
val state: StateFlow<String> = _state
fun updateText(newText: String) {
_state.value = newText // Модификация состояния
}
}В Jetpack Compose модификации (
Modifier) используются для изменения внешнего вида и поведения UI-элементов. Text(
text = "Привет!",
modifier = Modifier
.padding(16.dp) // Добавляем отступ
.background(Color.Blue) // Меняем фон
.clickable { println("Нажали!") } // Добавляем клик
)
В программировании модификация кода означает его улучшение без изменения основной логики.
fun sum(a: Int, b: Int): Int {
return a + b
}После рефакторинга
fun sum(a: Int, b: Int) = a + b
Это изменения в самой операционной системе Android, например:
- Рут-доступ (
root), позволяющий изменять системные файлы. - Кастомные прошивки (например, LineageOS).
- Изменение build.prop для скрытия рут-доступа.
ro.build.version.sdk=33
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊7👍1
Аннотация
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍2
Методы
equals() и hashCode() используются для сравнения объектов и их корректной работы в коллекциях (Set, Map). Метод
equals() должен:Рефлексивность:
a.equals(a) → true (объект равен самому себе). Симметричность:
a.equals(b) == b.equals(a). Транзитивность: если
a == b и b == c, то a == c. Согласованность: если
a == b, то a.equals(b) всегда возвращает одно и то же, пока объект не изменится. Сравнение с
null всегда даёт false: a.equals(null) == false. class User(val name: String, val age: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true // Проверка на ссылочное равенство
if (other !is User) return false // Проверка типа
return name == other.name && age == other.age // Сравнение полей
}
}val user1 = User("Alice", 25)
val user2 = User("Alice", 25)
println(user1 == user2) // true (потому что переопределён equals)Метод
hashCode() должен:Согласованность с
equals(): если a == b, то a.hashCode() == b.hashCode(). Но обратное не обязательно: два разных объекта могут иметь одинаковый
hashCode(). Хеш-код не должен меняться, если объект не изменился.
class User(val name: String, val age: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is User) return false
return name == other.name && age == other.age
}
override fun hashCode(): Int {
return name.hashCode() * 31 + age // 31 - стандартный коэффициент
}
}val userSet = HashSet<User>()
userSet.add(User("Alice", 25))
println(userSet.contains(User("Alice", 25))) // true
Чтобы не писать
equals() и hashCode() вручную, можно использовать data class: data class User(val name: String, val age: Int)
data class автоматически создаёт equals(), hashCode(), а также copy() и toString(). val user1 = User("Alice", 25)
val user2 = User("Alice", 25)
println(user1 == user2) // true (equals)
println(user1.hashCode() == user2.hashCode()) // trueСтавь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Наследование — это когда один класс расширяет другой, наследуя его поведение.
Композиция — это включение одного объекта в другой, при этом зависимость явно передаётся. Композиция считается более гибкой и менее связанной альтернативой наследованию.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
В Kotlin оператор
== используется для структурного сравнения объектов. Оператор
== вызывает метод equals(), чтобы проверить содержимое объектов, а не их ссылки. data class User(val name: String)
fun main() {
val user1 = User("Alice")
val user2 = User("Alice")
println(user1 == user2) // true (структурное сравнение)
println(user1 === user2) // false (сравнение ссылок)
}
== проверяет, равны ли данные объектов (equals()). === проверяет, ссылаются ли объекты на один и тот же участок памяти. val str1 = "Hello"
val str2 = "Hello"
println(str1 == str2) // true (содержимое одинаковое)
println(str1 === str2) // true (Kotlin кеширует строки)
val obj1 = String("Hello".toCharArray())
val obj2 = String("Hello".toCharArray())
println(obj1 == obj2) // true (содержимое одинаковое)
println(obj1 === obj2) // false (разные объекты в памяти)
Если
equals() не переопределён, то сравниваются ссылки (как ===). class Person(val name: String)
fun main() {
val p1 = Person("Bob")
val p2 = Person("Bob")
println(p1 == p2) // false (equals() не переопределён, сравниваются ссылки)
}
Чтобы
== работал по содержимому, нужно переопределить equals()class Person(val name: String) {
override fun equals(other: Any?): Boolean {
return other is Person && other.name == this.name
}
}
fun main() {
val p1 = Person("Bob")
val p2 = Person("Bob")
println(p1 == p2) // true (equals() сравнивает содержимое)
}Kotlin предотвращает
NullPointerException при сравнении с null:val a: String? = null
val b: String? = "Hello"
println(a == b) // false (без NPE)
println(a == null) // true
Когда
a == b, на самом деле выполняется:a?.equals(b) ?: (b === null)
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Нет, LayoutInflater — не синглтон, но может быть получен как скоуп-зависимый объект (getSystemService) в Context. Однако его можно переиспользовать, так как он не хранит состояния между вызовами.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊4🔥2
Разделение UI и бизнес-логики делает код понятнее, тестируемее и проще в поддержке.
Это ключевой принцип чистой архитектуры (Clean Architecture) и паттернов MVP, MVVM, MVI.
Плохой код (UI + логика в
Activity) 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 result = fetchData() // ❌ Логика в UI
button.text = result
}
}
fun fetchData(): String {
return "Данные с сервера" // ❌ Тут должна быть ViewModel
}
}Разделим код на
Activity + ViewModel MainActivity (только UI) class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
viewModel.text.observe(this) { text ->
button.text = text // ✅ UI обновляется из ViewModel
}
button.setOnClickListener {
viewModel.loadData() // ✅ Вызываем бизнес-логику
}
}
}MainViewModel (бизнес-логика) class MainViewModel : ViewModel() {
private val _text = MutableLiveData<String>()
val text: LiveData<String> = _text
fun loadData() {
_text.value = "Данные с сервера" // ✅ UI не знает, откуда данные
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
- В manifest:
- В коде:
Также можно использовать taskAffinity и фильтрацию в onNewIntent().
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1💊1
Ключевое слово
object в Kotlin имеет несколько важных применений, и оно является одной из наиболее мощных и уникальных возможностей языка. Kotlin предоставляет встроенную поддержку для создания singleton-объектов. Это объект, который имеет единственный экземпляр в приложении.
object Database {
val name = "MainDatabase"
fun connect() {
println("Подключение к базе данных $name")
}
}
fun main() {
Database.connect() // Подключение к базе данных MainDatabase
}Ключевое слово
object может использоваться для создания анонимных объектов (объектов без имени). Это полезно, если нужно создать временный объект или реализовать интерфейс/абстрактный класс.fun main() {
val listener = object : ClickListener {
override fun onClick() {
println("Кнопка нажата")
}
}
listener.onClick()
}
interface ClickListener {
fun onClick()
}Ключевое слово
object можно использовать внутри класса для объявления компаньон-объекта. Это позволяет создавать статические методы и переменные в классе.class User(val name: String) {
companion object {
fun createGuest() = User("Guest")
}
}
fun main() {
val guest = User.createGuest()
println(guest.name) // Guest
}Ключевое слово
object может использоваться для объявления объектов в коде прямо "на месте".val myObject = object {
val x = 10
fun printX() {
println(x)
}
}
fun main() {
myObject.printX() // 10
}Вместо написания множества шаблонного кода для Singleton, анонимных объектов или статических методов, вы получаете готовую реализацию из коробки.
В случае
object, компилятор Kotlin автоматически гарантирует, что объект создаётся в потокобезопасном режиме.object можно использовать практически везде: глобально, локально, внутри классов и функций.Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍1
Data Class и Sealed Class решают разные задачи и обеспечивают улучшения в организации кода, управлении состоянием и безопасности типов. Они вносят значительные упрощения и повышают читаемость кода в Kotlin-проектах.
Data Class предназначены для хранения данных и автоматически предоставляют ряд полезных методов, что упрощает разработку и уменьшает объем шаблонного кода. Основные причины использования Data Class:
Автоматически генерирует методы
equals(), hashCode(), и toString(), а также copy() и компонентные функции для объектов данных. Это избавляет от необходимости ручной реализации этих методов, что уменьшает количество кода и возможность ошибок.Идеально подходят для передачи данных между различными частями приложения, например, между слоями в архитектуре MVVM или при передаче данных между активностями и фрагментами.
С помощью них легко создавать неизменяемые объекты, что способствует безопасной работе с данными, особенно в многопоточной среде.
Sealed Class используются для определения закрытых иерархий классов, где все потомки известны и ограничены. Они полезны по следующим причинам:
Гарантируют, что все возможные подтипы обработаны в выражениях
when, что предотвращает ошибки во время выполнения из-за пропущенных случаев. Это упрощает управление состояниями и делает код более безопасным и предсказуемым.Ограничивают возможность создания подклассов за пределами файла, в котором они объявлены. Это предотвращает неожиданное наследование и сохраняет иерархию классов контролируемой и понятной.
Поскольку все расширения таких классов должны быть объявлены в том же файле, это способствует лучшей инкапсуляции и организации кода.
data class User(val name: String, val age: Int)
Пример Sealed Class
sealed class Result {
data class Success(val data: String) : Result()
data class Failure(val error: Throwable) : Result()
}
fun handleResult(result: Result) {
when (result) {
is Result.Success -> println("Success with data: ${result.data}")
is Result.Failure -> println("Failure with error: ${result.error.message}")
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Композиция создаётся внутри функции setContent, которая задает точку входа для composable функций в Activity или Fragment. Она инициирует рендеринг интерфейса и управление состоянием.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
Чтобы
Retrofit мог возвращать Observable, Single, Maybe или Flowable из RxJava, нужно добавить RxJava Adapter. В
build.gradle.kts (Kotlin DSL)dependencies {
implementation("com.squareup.retrofit2:adapter-rxjava3:2.9.0") // Адаптер для RxJava 3
implementation("io.reactivex.rxjava3:rxjava:3.1.8") // RxJava 3
}Добавляем адаптер в
Retrofit.Builderval retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create()) // Преобразование JSON
.addCallAdapterFactory(RxJava3CallAdapterFactory.create()) // Поддержка RxJava
.build()
Теперь можно возвращать RxJava-объекты вместо
Call<>. interface ApiService {
@GET("users/{id}")
fun getUser(@Path("id") userId: Int): Single<User>
}Пример с
Observable<> (несколько данных или обновления) interface ApiService {
@GET("users")
fun getUsers(): Observable<List<User>>
}Пример с
Flowable<> (если нужен Backpressure) interface ApiService {
@GET("posts")
fun getPosts(): Flowable<List<Post>>
}Пример подписки в
ViewModel (RxJava 3 + LiveData) class UserViewModel(private val apiService: ApiService) : ViewModel() {
private val _userLiveData = MutableLiveData<User>()
val userLiveData: LiveData<User> = _userLiveData
fun fetchUser(userId: Int) {
apiService.getUser(userId)
.subscribeOn(Schedulers.io()) // Запрос в фоновом потоке
.observeOn(AndroidSchedulers.mainThread()) // Обновление UI в главном потоке
.subscribe({ user ->
_userLiveData.value = user
}, { error ->
Log.e("UserViewModel", "Ошибка загрузки", error)
})
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
- App link: использует HTTP/HTTPS ссылки и требует подтверждения владения доменом через файл assetlinks.json.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Запрос данных из двух таблиц обычно выполняется с помощью операции объединения (JOIN) в SQL. Алгоритм и его сложность зависят от типа объединения, структуры данных и используемой базы данных.
Предположим, у нас есть две таблицы:
employees и departments. Мы хотим получить список сотрудников вместе с их отделами.SELECT employees.name, departments.name
FROM employees
INNER JOIN departments ON employees.department_id = departments.id;
Для каждой строки из первой таблицы выполняется поиск соответствующих строк во второй таблице.
for each row in employees:
for each row in departments:
if row.employees.department_id == row.departments.id:
yield (row.employees.name, row.departments.name)
Создается хеш-таблица для одной из таблиц на основе ключа соединения. Для каждой строки из другой таблицы проверяется наличие соответствующего ключа в хеш-таблице.
hash_table = {}
for each row in departments:
hash_table[row.id] = row.name
for each row in employees:
if row.department_id in hash_table:
yield (row.name, hash_table[row.department_id])Обе таблицы сортируются по ключу соединения, затем выполняется слияние отсортированных списков.
sorted_employees = sort(employees, key=lambda x: x.department_id)
sorted_departments = sort(departments, key=lambda x: x.id)
i, j = 0, 0
while i < len(sorted_employees) and j < len(sorted_departments):
if sorted_employees[i].department_id == sorted_departments[j].id:
yield (sorted_employees[i].name, sorted_departments[j].name)
i += 1
j += 1
elif sorted_employees[i].department_id < sorted_departments[j].id:
i += 1
else:
j += 1
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊3👍1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥2
В Kotlin есть три основных типа коллекций:
List — упорядоченный список элементов.
Set — множество уникальных элементов.
Map — коллекция пар "ключ-значение".
List — это коллекция, в которой можно хранить дубликаты, а элементы доступны по индексу.
val numbers = listOf(1, 2, 3, 4, 5) // Immutable (нельзя изменять)
val mutableNumbers = mutableListOf(1, 2, 3) // Можно изменять
mutableNumbers.add(4) // Добавляем элемент
mutableNumbers.removeAt(1) // Удаляем элемент по индексу
println(mutableNumbers) // [1, 3, 4]
Set — это коллекция, в которой нет дубликатов.
val numbers = setOf(1, 2, 3, 3, 4, 5) // Дубликаты удаляются автоматически
println(numbers) // [1, 2, 3, 4, 5]
val mutableNumbers = mutableSetOf(1, 2, 3)
mutableNumbers.add(3) // Не добавится, потому что уже есть
mutableNumbers.add(4) // Добавится
println(mutableNumbers) // [1, 2, 3, 4]
Map — это структура данных, в которой каждому ключу соответствует одно значение.
val userMap = mapOf(
1 to "Alice",
2 to "Bob",
3 to "Charlie"
) // Immutable
val mutableUserMap = mutableMapOf(1 to "Alice", 2 to "Bob")
mutableUserMap[3] = "Charlie" // Добавляем новый ключ-значение
mutableUserMap.remove(1) // Удаляем элемент по ключу
println(mutableUserMap) // {2=Bob, 3=Charlie}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Сервис не запустится без разрешения и foreground-нотификации, если он работает в фоне на Android 8.0 и выше. Также необходимо явно указать сервис в манифесте, и для долгоживущих задач использовать foregroundService, иначе система его завершит.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1
Когда используется deeplink, может возникнуть проблема, что одно и то же Activity может быть открыто несколько раз в стеке задач. Это происходит, если приложение запускается из внешнего источника (например, из браузера или другого приложения), и Android создает новую задачу или новую копию Activity вместо использования уже существующей. Чтобы справиться с этим, нужно правильно настроить
launchMode, интенты и флаги.При запуске приложения через deep link, система Android может:
1. Создать новый экземпляр вашего Activity (даже если оно уже существует в стеке задач).
2. Поместить новую задачу в стек задач.
Если это не контролировать, пользователь может увидеть много дубликатов одного и того же Activity, что плохо для UX и может вызвать утечку памяти.
В файле
AndroidManifest.xml можно настроить поведение Activity с помощью атрибута launchMode:singleTop: Если Activity уже находится на вершине стека, система не будет создавать новый экземпляр.singleTask: Убедитесь, что только один экземпляр Activity существует в задаче. Если Activity уже существует, система передаст интент в метод onNewIntent().singleInstance: Подходит для случаев, когда Activity должно быть абсолютно уникальным (используется редко).<activity
android:name=".MyActivity"
android:launchMode="singleTop" />
Если вы используете deep link или запускаете Activity вручную, можно добавить флаги, чтобы управлять созданием экземпляров:
val intent = Intent(this, MyActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
startActivity(intent)
Если используется
singleTop или singleTask, система вызывает метод onNewIntent(Intent intent) вместо создания нового экземпляра Activity. Этот метод можно переопределить для обработки новых данных из интента override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
// Обработка нового интента
val data = intent?.data
// Используйте данные deeplink
}
Если требуется, чтобы разные deep link открывали одну и ту же задачу, можно настроить
taskAffinity. Это нужно реже, но полезно, если нужно обрабатывать ссылки с разными контекстами.Дополнительно можно вручную проверять, существует ли нужное Activity в текущем состоянии приложения, например, используя LiveData или ViewModel.
Файл
AndroidManifest.xml<activity
android:name=".MyActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="example.com"
android:path="/deeplink" />
</intent-filter>
</activity>
Файл
MyActivity.ktclass MyActivity : AppCompatActivity() {
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
// Обработка данных из нового интента (deeplink)
val uri = intent?.data
uri?.let {
// Например, получить параметры из ссылки
val param = it.getQueryParameter("id")
Log.d("Deeplink", "Parameter id: $param")
}
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
ProGuard — это инструмент для:
- Минификации (сжатие имён классов, методов и полей),
- Удаления неиспользуемого кода,
- Обфускации (затруднение обратной декомпиляции кода).
В Android он часто используется в прод-сборках для защиты кода и уменьшения размера APK.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍1
В Kotlin инициализаторы используются для выполнения кода при создании экземпляра класса.
Первичный (primary) конструктор
Вторичные (secondary) конструкторы
Инициализационный блок (
init) Это основной способ инициализации класса в Kotlin. Он объявляется в заголовке класса.
class User(val name: String, val age: Int)
Если нужно выполнить дополнительную логику во время создания объекта, используют блок
init: class User(val name: String, val age: Int) {
init {
println("Создан пользователь: $name, возраст: $age")
}
}Они объявляются с помощью
constructor и нужны, если:Нужно несколько способов создания объекта.
Надо вызвать другой конструктор (
this(...)). class User {
var name: String
var age: Int
constructor(name: String) {
this.name = name
this.age = 18 // Значение по умолчанию
}
constructor(name: String, age: Int) {
this.name = name
this.age = age
}
}Можно создать объект так
val user1 = User("Алекс") // возраст будет 18
val user2 = User("Иван", 25) Если есть первичный конструктор, вторичный должен его вызывать через
this(...)class User(val name: String, val age: Int) {
constructor(name: String) : this(name, 18)
}init выполняется всегда при вызове первичного конструктора. Вторичный конструктор создаёт альтернативный способ создания объекта.
class User(val name: String, val age: Int) {
init {
println("Создан пользователь: $name, возраст: $age")
}
constructor(name: String) : this(name, 18) {
println("Вызван вторичный конструктор")
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🤔1