В 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
👍1
Контракты определяют, что два равных объекта (equals) должны иметь одинаковый hashCode. Нарушение этого принципа может привести к некорректной работе коллекций, таких как HashMap и HashSet.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1
В Retrofit, чтобы передать значение в определенное место URL-адреса для GET-запроса, нужно использовать аннотацию
@Path. Эта аннотация позволяет заменить параметр в строке пути на значение, переданное в метод.1. Внутри аннотации
@GET вы указываете строку пути, содержащую плейсхолдеры в фигурных скобках ({}).2. Аннотация
@Path связывает переменную метода с плейсхолдером в URL.3. Когда метод вызывается, значение переменной подставляется вместо плейсхолдера в URL.
Допустим, у вас есть API с эндпоинтом
https://api.example.com/users/{id}/detailsНастраиваем интерфейс Retrofit
interface ApiService {
@GET("users/{id}/details")
suspend fun getUserDetails(
@Path("id") userId: String
): Response<UserDetails>
}Теперь, когда вы вызываете этот метод, вы можете передать значение для
id, и Retrofit автоматически подставит его в URLval apiService = retrofit.create(ApiService::class.java)
suspend fun fetchUserDetails() {
val userId = "123" // Пример значения
val response = apiService.getUserDetails(userId)
if (response.isSuccessful) {
println("User details: ${response.body()}")
} else {
println("Error: ${response.errorBody()?.string()}")
}
}
Вы можете использовать несколько плейсхолдеров в пути и связать их с несколькими параметрами с помощью
@Path. Например:API-эндпоинт
https://api.example.com/users/{userId}/posts/{postId}Интерфейс Retrofit
interface ApiService {
@GET("users/{userId}/posts/{postId}")
suspend fun getPostDetails(
@Path("userId") userId: String,
@Path("postId") postId: String
): Response<PostDetails>
}Вызов метода
val response = apiService.getPostDetails("123", "456")
// URL: https://api.example.com/users/123/posts/456Если вы передаете
null или пустую строку в @Path, это вызовет ошибку, так как Retrofit требует обязательного значения для всех параметров пути. Убедитесь, что передаваемое значение всегда валидно.Если значение пути может содержать символы, требующие экранирования (например, пробелы, специальные символы), Retrofit автоматически обработает это с помощью кодировки URL. Это делает использование
@Path безопасным.@GET("search/{query}")
suspend fun search(
@Path("query") searchQuery: String
): Response<SearchResults>Если вы вызовете метод с поисковым запросом
apiService.search("hello world!")
// Retrofit закодирует URL: https://api.example.com/search/hello%20world%21Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
- Хранения информации в упорядоченном виде
- Эффективного выполнения операций: поиска, вставки, удаления
- Управления ресурсами: памятью, временем выполнения
- Выбора правильной модели хранения данных в зависимости от задачи (например, стек, очередь, дерево, хеш-таблица)
Они лежат в основе всех алгоритмов и приложений.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1
sealed class – это ограниченная иерархия классов, где можно создавать разные подклассы с разными свойствами. enum class – это фиксированный набор однотипных объектов, которые не имеют разной структуры. Когда значения не изменятся (например, дни недели, цвета, статусы).
Когда у всех значений одинаковая структура.
enum class Status {
LOADING, SUCCESS, ERROR
}Можно добавлять свойства и методы
enum class Color(val hex: String) {
RED("#FF0000"),
GREEN("#00FF00"),
BLUE("#0000FF");
fun printHex() = println(hex)
}
fun main() {
val color = Color.RED
println(color.hex) // #FF0000
color.printHex() // #FF0000
}Когда у состояний разные параметры и поведение.
Когда нужен
when, который проверяет все возможные подклассы. sealed class Status {
object Loading : Status()
data class Success(val data: String) : Status()
data class Error(val message: String) : Status()
}Использование с
when (без else) fun handleStatus(status: Status) {
when (status) {
is Status.Loading -> println("Загрузка...")
is Status.Success -> println("Данные: ${status.data}")
is Status.Error -> println("Ошибка: ${status.message}")
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Runnable представляет задачу, не возвращающую результата, и не выбрасывающую проверяемые исключения. Callable возвращает результат и может выбросить исключение — используется, когда требуется получить значение из фоновой задачи.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4
В Kotlin нет
switch, но его заменяет более мощный оператор when. Простой пример (аналог
switch-case) fun getDayName(day: Int): String {
return when (day) {
1 -> "Понедельник"
2 -> "Вторник"
3 -> "Среда"
4 -> "Четверг"
5 -> "Пятница"
6 -> "Суббота"
7 -> "Воскресенье"
else -> "Некорректный день"
}
}
fun main() {
println(getDayName(3)) // Среда
}when без else (если учтены все случаи) fun getStatus(code: Int): String = when (code) {
200 -> "OK"
404 -> "Not Found"
500 -> "Server Error"
}Несколько значений в одном
case fun isWeekend(day: Int): Boolean {
return when (day) {
6, 7 -> true // Оба значения работают как один case
else -> false
}
}Можно проверять логические выражения, а не просто числа.
fun checkNumber(x: Int): String {
return when {
x < 0 -> "Отрицательное число"
x == 0 -> "Ноль"
x > 0 -> "Положительное число"
else -> "Ошибка"
}
}Использование
when без аргумента fun describe(obj: Any): String {
return when {
obj is String -> "Это строка"
obj is Int -> "Это число"
obj is Boolean -> "Это логический тип"
else -> "Неизвестный тип"
}
}when можно использовать как выражение, возвращая результат: val message = when (val code = 404) {
200 -> "OK"
404 -> "Not Found"
else -> "Unknown"
}
println(message) // Not FoundСтавь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍3
Перегрузка может произойти при передаче функций с разным числом аргументов, noinline и crossinline, а также при компиляции в Java, где inline-функции превращаются в разные реализации. Коллизии имён могут быть причиной перегрузки.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Софткод (Softcode, Soft Coding) — это подход к программированию, при котором логика программы хранится в конфигурационных файлах, базе данных или других внешних источниках, а не жёстко (hardcoded) прописана в коде.
Пример Hardcode (жёстко зашито в коде)
val apiUrl = "https://api.example.com" // ❌ Если URL изменится, надо менять код
Пример Softcode (гибко через конфигурацию)
val apiUrl = Config.get("api_url") // ✅ Загружается из настроекКонфигурационные файлы (
config.json, .properties, .xml). База данных (логика, настройки, права пользователей).
API и сервер (получение UI-элементов, бизнес-логики с сервера).
Скриптовые языки (скрипты загружаются динамически).
Softcode через
SharedPreferences (конфигурация в памяти)val sharedPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
val theme = sharedPrefs.getString("app_theme", "light") // ✅ Загружаем тему из настроекSoftcode через
remoteConfig (Firebase Remote Config)val minVersion = Firebase.remoteConfig.getInt("min_supported_version")Softcode через JSON-файл (читаем конфиг из assets)
fun getConfigValue(context: Context, key: String): String {
val json = context.assets.open("config.json").bufferedReader().use { it.readText() }
val jsonObject = JSONObject(json)
return jsonObject.getString(key) // ✅ Получаем значение из JSON
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
DDP (Distributed Data Protocol) — используется в Meteor для реактивной передачи данных между клиентом и сервером. Поддерживает WebSocket и JSON-формат, обеспечивает автоматическую синхронизацию.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
В Kotlin Flow есть два специальных вида потоков для управления состоянием и передачей данных:
StateFlow — используется для хранения и отслеживания состояния.
SharedFlow — используется для многократной отправки данных нескольким подписчикам.
Теперь разберёмся в деталях.
SharedFlow — это горячий (hot) поток, который можно использовать для передачи данных нескольким подписчикам. Он не хранит состояние и просто раздаёт значения подписчикам в реальном времени.
- Позволяет многим подписчикам получать одни и те же данные.
- Может буферизировать значения (хранить их для новых подписчиков).
- Может повторять последние значения (replay) для новых подписчиков.
- Может накапливать данные и работать как очередь событий.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val sharedFlow = MutableSharedFlow<Int>(replay = 2) // Будет повторять последние 2 значения для новых подписчиков
launch {
for (i in 1..5) {
sharedFlow.emit(i)
delay(100)
}
}
launch {
delay(150) // Подписываемся чуть позже
sharedFlow.collect { println("Первый подписчик получил: $it") }
}
launch {
delay(300) // Подписываемся ещё позже
sharedFlow.collect { println("Второй подписчик получил: $it") }
}
}
StateFlow — это поток, который всегда хранит одно последнее значениЕ. Он идеально подходит для представления состояния (например, UI-состояния в MVVM).
- Всегда содержит одно актуальное значение.
- Если новое значение не отличается от текущего, оно не отправляется подписчикам.
- Новый подписчик сразу получает текущее значение.
- Можно думать о StateFlow как о LiveData, но для корутин.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val stateFlow = MutableStateFlow(0) // Начальное состояние
launch {
delay(200) // Подписываемся позже
stateFlow.collect { println("Подписчик получил: $it") }
}
delay(100)
stateFlow.value = 1 // Меняем состояние
stateFlow.value = 2 // Меняем состояние
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍4
Данные хранятся в SharedPreferences, SQLite/Room, файлах, в базе данных на сервере, а также в DataStore, EncryptedSharedPreferences и KeyStore — в зависимости от нужной степени надёжности и структуры данных.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
1💊3👍2🔥1
Методы
get и replace в Kotlin относятся к работе с коллекциями, карты (Map) или к строкам. В зависимости от контекста, они вызываются через разные классы. Давайте разберем каждый случай отдельно.В контексте работы с картами (
Map), методы get и replace относятся к получению значений по ключу и замене существующих значений.Метод
getМетод
get используется для извлечения значения из карты по указанному ключу.val map = mapOf("key1" to "value1", "key2" to "value2")
println(map.get("key1")) // value1
println(map["key2"]) // value2 (альтернатива `get`)Метод
replace используется для обновления значения, связанного с определённым ключом, если он существует. Этот метод доступен только для изменяемых карт (MutableMap).val mutableMap = mutableMapOf("key1" to "value1", "key2" to "value2")
mutableMap.replace("key1", "newValue1")
println(mutableMap) // {key1=newValue1, key2=value2}В контексте строк, методы
get и replace работают с символами и подстроками.Метод
get используется для доступа к символу строки по индексу. Это альтернатива квадратным скобкам.val text = "Kotlin"
println(text.get(0)) // K
println(text[1]) // o (альтернатива `get`)
Метод
replace заменяет символы или подстроки в строке на заданные.val text = "Kotlin is awesome"
val newText = text.replace("awesome", "powerful")
println(newText) // Kotlin is powerful
Если вы пишете свои классы, вы можете переопределить оператор
get и метод replace, чтобы использовать их для своих нужд.class CustomList<T>(private val items: List<T>) {
operator fun get(index: Int): T {
return items[index]
}
}
fun main() {
val customList = CustomList(listOf(1, 2, 3))
println(customList[0]) // 1
}Пример с
replaceclass CustomMap<K, V>(private val map: MutableMap<K, V>) {
fun replace(key: K, value: V) {
if (map.containsKey(key)) {
map[key] = value
}
}
}
fun main() {
val customMap = CustomMap(mutableMapOf("key1" to "value1"))
customMap.replace("key1", "newValue1")
println(customMap) // {key1=newValue1}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
- Основной поток приложения, где выполняются все операции с пользовательским интерфейсом.
- Долгие операции здесь могут привести к замораживанию приложения.
2. Worker thread:
- Фоновые потоки для выполнения долгих задач (например, обработки данных, запросов в сеть).
- Обновление UI из фонового потока невозможно.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
Да! В Android есть специальные Map-коллекции, которые позволяют хранить примитивные типы (
int, long, boolean и т. д.) без автоупаковки (autoboxing). Обычные
HashMap<Int, Int> в Kotlin используют автоупаковку (Integer вместо int), что: Увеличивает потребление памяти (из-за объектов
Integer, Long и т. д.). Замедляет работу (из-за ненужного создания объектов).
Решение? Использовать специализированные мэпы из
android.util! Хранит пары
Int → Any, но без автоупаковки. import android.util.SparseArray
val sparseArray = SparseArray<String>()
sparseArray.put(1, "Привет")
sparseArray.put(2, "Мир")
println(sparseArray[1]) // Привет
println(sparseArray[2]) // Мир
Хранит пары
Int → Int без автоупаковки. import android.util.SparseIntArray
val sparseIntArray = SparseIntArray()
sparseIntArray.put(1, 100)
sparseIntArray.put(2, 200)
println(sparseIntArray[1]) // 100
println(sparseIntArray[2]) // 200
Оптимизирован для
Int → Boolean пар. import android.util.SparseBooleanArray
val sparseBooleanArray = SparseBooleanArray()
sparseBooleanArray.put(1, true)
sparseBooleanArray.put(2, false)
println(sparseBooleanArray[1]) // true
println(sparseBooleanArray[2]) // false
Оптимизирован для
Long → Any?, аналог SparseArray, но с Long ключами. import android.util.LongSparseArray
val longSparseArray = LongSparseArray<String>()
longSparseArray.put(10000000000L, "Длинный ключ")
println(longSparseArray[10000000000L]) // Длинный ключ
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Код генерируется на этапе компиляции, благодаря аннотациям (
Room использует аннотационный процессор, который создает вспомогательные классы для доступа к базе, проверяет запросы и формирует безопасный API.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Чтобы
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
👍1
WorkManager начинает выполнять задачу:
- Когда соблюдены все заданные условия (например, наличие сети, заряд батареи).
- После планирования задачи (enqueuing).
- Даже если приложение было перезапущено — WorkManager восстанавливает задачу.
- Если используется отложенная работа — срабатывает по расписанию или через заданный интервал.
Работает надёжно даже после перезагрузки устройства.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1
Да, потеря состояния (state) может быть связана с фрагментами (Fragments) в Android. Это довольно распространённая проблема, особенно при работе с динамическими интерфейсами. Давайте разберёмся, почему она возникает, как её предотвратить и какие решения существуют.
Фрагменты имеют сложный жизненный цикл, который тесно связан с активностью. Основные этапы:
onCreate() — создаётся фрагмент, инициализируются объекты.onViewCreated() — создаётся View (UI компоненты).onStart() / onResume() — фрагмент становится видимым и активным.onPause() / onStop() — фрагмент приостанавливается.onDestroyView() — уничтожается только View (UI), но сам фрагмент всё ещё существует.onDestroy() — полностью уничтожается фрагмент.Фрагменты могут пересоздаваться системой, например, при смене ориентации экрана или нехватке памяти. Если разработчик неправильно сохраняет состояние фрагмента, оно может быть утеряно.
Когда фрагмент переходит в состояние
onDestroyView(), его View уничтожается, но сам объект фрагмента сохраняется. Если пользователь вернётся к этому фрагменту, View будет пересоздана, и вы потеряете все изменения, сделанные ранее, если они не сохранены явно.Использование
FragmentManager или FragmentTransaction с неправильными методами, такими как replace() или add(), без должного управления стэком (back stack), может привести к пересозданию или дублированию фрагментов, что вызывает потерю состояния.Фрагменты, как и активности, используют механизм сохранения состояния через
Bundle (в onSaveInstanceState). Если состояние не сохранено правильно, данные могут быть потеряны.При уничтожении фрагмента система вызывает метод
onSaveInstanceState(). Здесь вы можете сохранить важные данные в Bundle.override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("key_text", editText.text.toString())
}При пересоздании фрагмента данные можно восстановить в
onViewCreated()override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val restoredText = savedInstanceState?.getString("key_text")
editText.setText(restoredText)
}Для хранения состояния, которое переживает уничтожение и пересоздание фрагмента, лучше использовать архитектурный компонент
ViewModel.ViewModelclass MyViewModel : ViewModel() {
val textData = MutableLiveData<String>()
}class MyFragment : Fragment() {
private lateinit var viewModel: MyViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
// Восстановление данных
viewModel.textData.observe(viewLifecycleOwner) { text ->
editText.setText(text)
}
// Сохранение данных при изменении
editText.addTextChangedListener {
viewModel.textData.value = it.toString()
}
}
}При работе с фрагментами всегда добавляйте их в back stack, если вы хотите сохранить состояние.
val fragment = MyFragment()
parentFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment)
.addToBackStack(null)
.commit()
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Через Gradle:
1. Компиляция исходников,
2. Обработка ресурсов,
3. Генерация R и BuildConfig,
4. Объединение в .apk или .aab,
5. Подпись и выравнивание.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2