Kotlin | Вопросы собесов
2.53K subscribers
29 photos
1K links
Download Telegram
🤔 Когда происходит перегрузка при использовании inline-функций?

Перегрузка может произойти при передаче функций с разным числом аргументов, noinline и crossinline, а также при компиляции в Java, где inline-функции превращаются в разные реализации. Коллизии имён могут быть причиной перегрузки.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
🤔 Что такое софткод?

Софткод (Softcode, Soft Coding) — это подход к программированию, при котором логика программы хранится в конфигурационных файлах, базе данных или других внешних источниках, а не жёстко (hardcoded) прописана в коде.

🚩Разница между Hardcode и Softcode

Пример Hardcode (жёстко зашито в коде)
val apiUrl = "https://api.example.com" //  Если URL изменится, надо менять код


Пример Softcode (гибко через конфигурацию)
val apiUrl = Config.get("api_url") //  Загружается из настроек


🚩Где используется софткод?

Конфигурационные файлы (config.json, .properties, .xml).
База данных (логика, настройки, права пользователей).
API и сервер (получение UI-элементов, бизнес-логики с сервера).
Скриптовые языки (скрипты загружаются динамически).

🚩Примеры использования Softcode в Android

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-протокола?

DDP (Distributed Data Protocol) — используется в Meteor для реактивной передачи данных между клиентом и сервером. Поддерживает WebSocket и JSON-формат, обеспечивает автоматическую синхронизацию.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
🤔 Что такое SharedFlow и чем он отличается от StateFlow?

В Kotlin Flow есть два специальных вида потоков для управления состоянием и передачей данных:
StateFlow — используется для хранения и отслеживания состояния.
SharedFlow — используется для многократной отправки данных нескольким подписчикам.
Теперь разберёмся в деталях.

🚩Что такое SharedFlow?

SharedFlow — это горячий (hot) поток, который можно использовать для передачи данных нескольким подписчикам. Он не хранит состояние и просто раздаёт значения подписчикам в реальном времени.

🚩Как работает SharedFlow?

- Позволяет многим подписчикам получать одни и те же данные.
- Может буферизировать значения (хранить их для новых подписчиков).
- Может повторять последние значения (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?

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
🤔 Как происходит хранение данных в Android?

Данные хранятся в SharedPreferences, SQLite/Room, файлах, в базе данных на сервере, а также в DataStore, EncryptedSharedPreferences и KeyStore — в зависимости от нужной степени надёжности и структуры данных.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
1💊3👍2🔥1
🤔 Через какой класс вызываются методы get, replace?

Методы get и replace в Kotlin относятся к работе с коллекциями, карты (Map) или к строкам. В зависимости от контекста, они вызываются через разные классы. Давайте разберем каждый случай отдельно.

🟠Методы `get` и `replace` для коллекций (`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 и 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` через пользовательские классы
Если вы пишете свои классы, вы можете переопределить оператор 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
}


Пример с replace
class 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
🤔 Что такое UI thread и Worker thread?

1. UI thread:
- Основной поток приложения, где выполняются все операции с пользовательским интерфейсом.
- Долгие операции здесь могут привести к замораживанию приложения.
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!

🚩`SparseArray` (замена `HashMap<Int, Any?>`)

Хранит пары Int → Any, но без автоупаковки.
import android.util.SparseArray

val sparseArray = SparseArray<String>()
sparseArray.put(1, "Привет")
sparseArray.put(2, "Мир")

println(sparseArray[1]) // Привет
println(sparseArray[2]) // Мир


🚩`SparseIntArray` (замена `HashMap<Int, Int>`)

Хранит пары 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


🚩`SparseBooleanArray` (замена `HashMap<Int, Boolean>`)

Оптимизирован для 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


🚩`LongSparseArray<T>` (замена `HashMap<Long, T>`)

Оптимизирован для 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
🤔 В какой момент генерируется код при использовании SQLite (Room)?

Код генерируется на этапе компиляции, благодаря аннотациям (
@Entity, @Dao, @Database).
Room использует аннотационный процессор, который создает вспомогательные классы для доступа к базе, проверяет запросы и формирует безопасный API.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
🤔 Как в Ретрофите добавить возможность возвращать типы Rxjava?

Чтобы 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
}


🚩Подключение `RxJava3CallAdapterFactory`

Добавляем адаптер в Retrofit.Builder
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create()) // Преобразование JSON
.addCallAdapterFactory(RxJava3CallAdapterFactory.create()) // Поддержка RxJava
.build()


🚩Использование RxJava в API

Теперь можно возвращать 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?

WorkManager начинает выполнять задачу:
- Когда соблюдены все заданные условия (например, наличие сети, заряд батареи).
- После планирования задачи (enqueuing).
- Даже если приложение было перезапущено — WorkManager восстанавливает задачу.
- Если используется отложенная работа — срабатывает по расписанию или через заданный интервал.
Работает надёжно даже после перезагрузки устройства.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1
🤔 Может ли потеря state быть связанной с фрагментом?

Да, потеря состояния (state) может быть связана с фрагментами (Fragments) в Android. Это довольно распространённая проблема, особенно при работе с динамическими интерфейсами. Давайте разберёмся, почему она возникает, как её предотвратить и какие решения существуют.

🚩Почему происходит потеря состояния во фрагментах?

🟠Жизненный цикл фрагмента
Фрагменты имеют сложный жизненный цикл, который тесно связан с активностью. Основные этапы:
onCreate() — создаётся фрагмент, инициализируются объекты.
onViewCreated() — создаётся View (UI компоненты).
onStart() / onResume() — фрагмент становится видимым и активным.
onPause() / onStop() — фрагмент приостанавливается.
onDestroyView() — уничтожается только View (UI), но сам фрагмент всё ещё существует.
onDestroy() — полностью уничтожается фрагмент.
Фрагменты могут пересоздаваться системой, например, при смене ориентации экрана или нехватке памяти. Если разработчик неправильно сохраняет состояние фрагмента, оно может быть утеряно.

🟠Удаление View фрагмента системой
Когда фрагмент переходит в состояние onDestroyView(), его View уничтожается, но сам объект фрагмента сохраняется. Если пользователь вернётся к этому фрагменту, View будет пересоздана, и вы потеряете все изменения, сделанные ранее, если они не сохранены явно.

🟠Проблемы с менеджером фрагментов
Использование FragmentManager или FragmentTransaction с неправильными методами, такими как replace() или add(), без должного управления стэком (back stack), может привести к пересозданию или дублированию фрагментов, что вызывает потерю состояния.

🟠Проблемы с `savedInstanceState`
Фрагменты, как и активности, используют механизм сохранения состояния через BundleonSaveInstanceState). Если состояние не сохранено правильно, данные могут быть потеряны.

🚩Как предотвратить потерю состояния во фрагментах?

🟠Сохранение данных в `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`
Для хранения состояния, которое переживает уничтожение и пересоздание фрагмента, лучше использовать архитектурный компонент ViewModel.
1⃣Создайте ViewModel
class MyViewModel : ViewModel() {
val textData = MutableLiveData<String>()
}


2⃣Используйте его во фрагменте
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()
}
}
}


🟠Использование `FragmentManager` правильно
При работе с фрагментами всегда добавляйте их в 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
👍3
🤔 Как собираем код?

Через Gradle:
1. Компиляция исходников,
2. Обработка ресурсов,
3. Генерация R и BuildConfig,
4. Объединение в .apk или .aab,
5. Подпись и выравнивание.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
🤔 В чем различие Default диспатчер и IO диспатчер?

В Kotlin Coroutines есть несколько диспетчеров (Dispatchers), но Default и IO используются чаще всего.

🚩Главное различие:

Dispatchers.Default — для тяжёлых вычислений (CPU-операции).
Dispatchers.IO — для операций ввода-вывода (сеть, файлы, БД).

🟠`Dispatchers.Default` – для сложных вычислений (CPU-bound)
Этот диспетчер используется, если код загружает процессор (например, сложные вычисления).
import kotlinx.coroutines.*

fun main() = runBlocking {
launch(Dispatchers.Default) {
val result = heavyComputation()
println("Результат: $result")
}
}

suspend fun heavyComputation(): Int {
delay(1000)
return (1..1_000_000).sum()
}


🟠`Dispatchers.IO` – для работы с файлами, сетью, БД (IO-bound)
Этот диспетчер оптимизирован для ввода-вывода (I/O): работа с файлами, сетью, БД.
import kotlinx.coroutines.*
import java.io.File

fun main() = runBlocking {
launch(Dispatchers.IO) {
val text = File("data.txt").readText()
println("Файл прочитан: $text")
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🤔 Почему необходимо использовать @Binds вместо @Provide?

@Binds используется, когда реализация уже существует, и требуется просто сопоставить её с интерфейсом. Это более оптимально по производительности и не требует тела метода, в отличие от @Provide, который нужен, если логика создания объекта выполняется вручную.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2👍1
🤔 Как называется лейаут в котором объекты могут наслаиваться друг на друга?

В Android для наложения (перекрытия) элементов друг на друга используется FrameLayout или Box (в Jetpack Compose).

🚩FrameLayout (в XML и View)

FrameLayout — это контейнер, в котором все вложенные элементы располагаются в левом верхнем углу, но при этом могут накладываться друг на друга.
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/background" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Наложенный текст"
android:textSize="24sp"
android:textColor="#FFFFFF"
android:layout_gravity="center"/>
</FrameLayout>


🚩Box (в Jetpack Compose)

В Jetpack Compose аналогом FrameLayout является Box. Он также позволяет располагать элементы друг над другом.
Box(
modifier = Modifier.fillMaxSize()
) {
Image(
painter = painterResource(id = R.drawable.background),
contentDescription = "Фон",
modifier = Modifier.fillMaxSize()
)

Text(
text = "Наложенный текст",
fontSize = 24.sp,
color = Color.White,
modifier = Modifier.align(Alignment.Center)
)
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🤔 Как работает SOLID-принцип SRP?

Принцип единственной ответственности (Single Responsibility Principle) гласит, что класс должен иметь одну, и только одну, причину для изменения. Это означает:
1. Класс должен выполнять только одну задачу или отвечать за один аспект функциональности.
2. Изменения в одной части функционала не должны влиять на другие аспекты.
3. Это упрощает сопровождение, тестирование и повторное использование кода.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
🤔 Как определить изменение скорости работы программы после наших действий?

🟠Profile GPU Rendering
Этот инструмент показывает время, затраченное на отрисовку каждого кадра. Использование этого инструмента позволяет выявить "тяжелые" кадры и измерить улучшения после оптимизации.

1⃣Включите режим разработчика на устройстве.
2⃣Перейдите в "Настройки" -> "Для разработчиков".
3⃣Включите опцию "Profile GPU Rendering" и выберите "On screen as bars".
4⃣Запустите ваше приложение и наблюдайте за графиками. Зеленые линии указывают на время отрисовки, и ваша цель - оставаться ниже 16 мс (для 60 кадров в секунду).

🟠Android Profiler
Предоставляет набор инструментов для анализа производительности приложения.

1⃣ Откройте Android Studio и запустите ваше приложение.
2⃣Перейдите в "View" -> "Tool Windows" -> "Profiler".
3⃣Выберите ваше устройство и запущенное приложение.
4⃣Используйте вкладки CPU, Memory, Network и Energy для анализа различных аспектов производительности.
5⃣Сравните данные до и после оптимизации.

🟠Benchmarking
Создание и использование тестов производительности помогает количественно оценить улучшения. Вы можете использовать библиотеку Jetpack Benchmark для создания и выполнения тестов производительности.

🚩Пример использования Jetpack Benchmark:

1⃣Добавьте зависимости в build.gradle:
dependencies {
androidTestImplementation "androidx.benchmark:benchmark-junit4:1.1.0"
androidTestImplementation "androidx.test:runner:1.3.0"
androidTestImplementation "androidx.test:rules:1.3.0"
}


2⃣Создайте класс теста:
import androidx.benchmark.junit4.BenchmarkRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ExampleBenchmark {
@get:Rule
val benchmarkRule = BenchmarkRule()

@Test
fun myFunctionBenchmark() {
benchmarkRule.measureRepeated {
// Вызов вашей функции или кода для тестирования производительности
myFunction()
}
}
}


🟠Logcat
Используйте журналирование для измерения времени выполнения определенных операций.
val startTime = System.currentTimeMillis()
// Ваш код
val endTime = System.currentTimeMillis()
Log.d("Performance", "Время выполнения: ${endTime - startTime} мс")


🟠StrictMode
StrictMode помогает обнаружить операции, которые могут замедлить работу приложения, такие как работа с сетью или базой данных в главном потоке.
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
}


🟠Systrace
Systrace позволяет собирать и анализировать трассировки производительности системы, предоставляя детализированные данные о времени выполнения различных операций.

1⃣Включите режим разработчика на устройстве.
2⃣В "Настройки" -> "Для разработчиков" включите "Enable GPU Debug Layers".
3⃣Запустите команду adb shell am broadcast -a com.android.systemui.screenshot.ScreenshotService.ACTION_SYSTRACE.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 Как бороться с проблемой backpressure в RxJava?

Проблема решается использованием Flowable вместо Observable, поскольку Flowable поддерживает стратегию управления нагрузкой. Также применяются операторы onBackpressureBuffer, onBackpressureDrop, onBackpressureLatest и управление запросами вручную.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2👍1