Библиотека мобильного разработчика | Android, iOS, Swift, Retrofit, Moshi, Chuck
9.57K subscribers
1.68K photos
82 videos
52 files
4.5K links
Все самое полезное для мобильного разработчика в одном канале.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/b60af5a4

Для обратной связи: @proglibrary_feeedback_bot

РКН: https://gosuslugi.ru/snet/67a4adec1b17b35b6c0d8389
Download Telegram
📌 Ключевое слово reified

reified — это ключевое слово, которое может быть использовано только в inline-функциях. reified позволяет получить информацию о типе generic-параметра во время выполнения программы. В обычном случае, информация о типах стирается и недоступна во время выполнения, но с помощью reified можно сохранять эту информацию и использовать в других частях приложения.

Несколько простых примеров применения:

1. Получить доступ к типу параметра во время выполнения

fun main() {
printType<String>() // String
printType<Int>() // Int
}

private inline fun <reified T> printType() {
println(T::class.simpleName)
}


В этом примере мы определяем функцию printType() с типовым параметром T, который мы указываем с помощью reified. Внутри функции мы можем получить тип T во время выполнения, используя T::class. Затем выводим название типа на экран с помощью simpleName. Когда мы вызываем функцию printType() с типом String или Int, она выводит соответствующий тип на экран.

2. reified вместе с is для проверки типа аргумента во время выполнения

fun main() {
println(isOfType<Int>(1)) // true
println(isOfType<Int>("Hello")) // false
}

private inline fun <reified T> isOfType(value: Any): Boolean {
return value is T
}


Здесь мы определяем функцию isOfType(), которая принимает значение типа Any и возвращает true, если оно является типом T. Мы используем reified, чтобы получить доступ к типу T во время выполнения. Затем мы используем оператор is для проверки типа значения и возвращаем соответствующее boolean значение.

3. Получить список элементов перечисления

enum class Color { RED, GREEN, BLUE }

fun main() {
printEnumValues<Color>() // RED, GREEN, BLUE
}

private inline fun <reified T : Enum<T>> printEnumValues() {
enumValues<T>().forEach { value ->
println(value)
}
}


Определяем функцию printEnumValues(), которая выводит список элементов перечисления типа T. Мы применяем reified, чтобы получить доступ к типу T во время выполнения. Затем используем enumValues<T>(), чтобы получить список всех значений перечисления типа T. Внутри цикла выводим каждое значение на экран. Когда мы вызываем функцию printEnumValues() с типом Color, она выводит "RED", "GREEN" и "BLUE" в консоль.

🐸 Библиотека мобильного разработчика

#буст #JuniorKit #Kotlin
Please open Telegram to view this post
VIEW IN TELEGRAM
2
🤡 Побочные эффекты в Jetpack Compose — простое объяснение

В Jetpack Compose не рекомендуется напрямую вызывать не-компонуемые функции внутри composable-функций.

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

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

Ниже приведены самые распространённые обработчики эффектов в Compose — просто и с примерами.

🔹 SideEffect

Выполняет логику после каждого успешного пересоздания (recomposition).

SideEffect {
Log.d("TAG", "Recomposition completed")
}


Подходит для логирования, аналитики или любых операций, которые должны выполняться после отрисовки интерфейса.

🔹 LaunchedEffect

Запускает корутину, когда изменяется указанный ключ. Если происходит пересоздание и ключ меняется — предыдущая корутина отменяется, и запускается новая.

LaunchedEffect(key1 = someState) {
fetchData()
}


Идеально подходит для вызова API или выполнения логики, зависящей от изменяющегося состояния.

🔹 rememberCoroutineScope

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

val coroutineScope = rememberCoroutineScope()
Button(onClick = {
coroutineScope.launch {
doSomething()
}
}) {
Text("Click me")
}


Для событий, инициируемых пользователем, которые не зависят напрямую от состояния или жизненного цикла.

🔹 DisposableEffect

Выполняет код при входе в композицию и очищает ресурсы при выходе из неё.

DisposableEffect(key1 = someKey) {
startListening()


    onDispose {
stopListening()
}
}


Добавление или удаление слушателей, наблюдателей, освобождение внешних ресурсов и другая логика очистки.

🐸 Библиотека мобильного разработчика

#буст #JuniorKit #Android
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🔒 Основные сценарии использования Mutex в Kotlin

Когда несколько корутин обращаются к общим данным или ресурсам одновременно, возникает риск гонок данных (race conditions).

Mutex (mutual exclusion) — это инструмент синхронизации, который помогает управлять доступом к общим ресурсам, гарантируя, что в один момент времени их изменяет только одна корутина.

Вот самые частые примеры, когда Mutex действительно нужен:

1️⃣ Защита разделяемого изменяемого состояния

Самый распространённый случай — безопасный доступ к общей переменной:

class CounterService {
private var counter = 0
private val mutex = Mutex()

suspend fun increment() {
mutex.withLock {
counter++
}
}

suspend fun getCount(): Int {
return mutex.withLock {
counter
}
}
}


2️⃣ Координация доступа к ресурсу

Когда несколько корутин должны поочерёдно работать с общим ресурсом:

class FileWriter(private val file: File) {
private val mutex = Mutex()

suspend fun appendLine(line: String) {
mutex.withLock {
file.appendText("$line\n")
}
}
}


3️⃣ Обеспечение последовательного выполнения

Когда операции должны выполняться строго по порядку, даже если запущены одновременно:

class OrderProcessor {
private val mutex = Mutex()
private val orders = mutableListOf<Order>()

suspend fun processOrder(order: Order) {
mutex.withLock {
// Обеспечиваем последовательную обработку заказов
orders.add(order)
validateOrder(order)
persistOrder(order)
}
}
}


4️⃣ Отложенная инициализация с защитой потоков

Обеспечивает безопасную инициализацию ресурса в асинхронных контекстах:

class DatabaseConnection {
private var connection: Connection? = null
private val mutex = Mutex()

suspend fun getConnection(): Connection {
if (connection != null) return connection!!

return mutex.withLock {
// Повторная проверка внутри блокировки
connection ?: createConnection().also { connection = it }
}
}

private suspend fun createConnection(): Connection {
delay(1000) // Симуляция установки соединения
return Connection()
}
}


🐸 Библиотека мобильного разработчика

#АрхитектурныйКод #JuniorKit #Kotlin
Please open Telegram to view this post
VIEW IN TELEGRAM
👍51
Какой тип находится на вершине иерархии типов в Kotlin

Аналогично Object в Java, к чему можно привести любой тип в Kotlin?
Правильным ответом будет Any?.

Сам по себе класс Any это почти аналог Object, однако, благодаря поддержке nullable и не-nullable типов в Kotlin мы получили Any?. Фактически, Any? соответствует любому типу и null, а Any только любому типу.

Если по порядку:

1. Any является корнем иерархии не-nullable типов.

2. Any? является корнем иерархии nullable типов.

3. Так как Any? является супертипом Any, то Any? находится в самом верху иерархии типов в Kotlin.

🐸 Библиотека мобильного разработчика

#буст #JuniorKit #Android
Please open Telegram to view this post
VIEW IN TELEGRAM
4👍3🔥2
Что такое дженерики

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

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

Именно здесь вы можете использовать дженерики.

Например, вы можете создать общий тип для параметров (для представления любого типа), используя букву, например T, следующим образом:

struct Vec3D<T> {
let x, y, z: T
init(x: T, y: T, z: T) {
self.x = x
self.y = y
self.z = z
}
}
let intVector = Vec3D(x: 1, y: 2, z: 5)
let floatVector = Vec3D(x: 1.0, y: 2.0, z: 5.0)


🐸 Библиотека мобильного разработчика

#буст #JuniorKit #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM
1
📎 Разница между лямбда-выражением и анонимной функцией

1️⃣ Синтаксис

Лямбда-выражения определяются заключением их в фигурные скобки в виде { параметры -> тело }.

Анонимные функции определяются через ключевое слово fun как обычные функции, хотя не имеют имени.

2️⃣ Поведение оператора return без метки

🔘 В лямбда-выражении использование оператора return без метки приводит к возврату из обрамляющей (внешней) функции, а не из самого лямбда-выражения (т.е. полностью завершает работу этой функции и код, указанный после оператора return никогда не выполнится). Это называется нелокальным возвратом (non-local return), и может иметь неожиданное поведение и привести к ошибкам. В лямбда-выражениях рекомендуется использовать метки для явного указания точки возврата.

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

Таким образом, поведение оператора return без метки различается только в том, как завершается выполнение функции, в которой вызывается выражение. В лямбда-выражении, вызывающая функция завершается полностью, а в анонимной функции только сама анонимная функция.

3️⃣ Поведение оператора return с меткой

Оператор return с меткой позволяет указать точное место, из которого нужно вернуться при вызове return.

🔘 Если использовать return@label в лямбда-выражении, то возврат будет осуществляться из конкретной лямбды, к которой применена метка. Вместо нелокального возврата, который происходит при использовании return без метки, return с меткой завершит только ту лямбду, которая соответствует указанной метке, и выполнение кода продолжится после этой лямбды во внешней функции. Метка позволяет читать и понимать код проще, так как явно указывает, откуда происходит возврат.

🔘 В анонимных функциях return без метки уже осуществляет возврат из самой анонимной функции. Однако, при использовании метки return@label вы также можете контролировать возврат из анонимной функции в сложных сценариях (например, при работе с несколькими вложенными функциями).

В обоих случаях использование оператора return с меткой показывает точку возврата и делает код более явным и контролируемым.

👉 Подробнее о возврате к меткам

Библиотека мобильного разработчика

#буст #JuniorKit #Kotlin
Please open Telegram to view this post
VIEW IN TELEGRAM
🥰4
Предложите небольшую доработку для следующего кода

if age >= 18 {
driveCar()
} else {
doNotDrive()
}


Этот код хорошо работает - но можете ли вы предложить небольшое улучшение рефакторинга, чтобы сделать его еще лучше?

Вы можете использовать тернарный условный оператор для преобразования этого выражения в однострочное, что в данном случае не ухудшает читабельность, а улучшает ее.

age >= 18 ? driveCar() : doNotDrive()


🐸 Библиотека мобильного разработчика

#буст #JuniorKit #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱62😢2🤔1
Что такое полиморфизм

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

class Person{
var name: String
var age: Int

init(name: String, age: Int){
self.name = name
self.age = age
}
func display(){
print("Имя: \(name) Возраст: \(age)")
}
}

class Employee : Person{
var company: String
init(name: String, age: Int, company: String) {
self.company = company
super.init(name:name, age: age)
}
override func display(){
print("Имя: \(name) Возраст: \(age) Сотрудник компании: \(company)")
}
}

class Manager : Employee{
override func display(){
print("Имя: \(name) Возраст: \(age) Менеджер компании: \(company)")
}
}


В данном случае класс Manager (менеджер компании) наследуется от класса Employee (сотрудник компании), а класс Employee - от класса Person (человек). Тем самым класс Manager ненапрямую тоже наследуется от Person.

Поскольку и сотрудник компании и менеджер компании в то же время являются людьми, то есть объектами класса Person, то мы можем написать следующим образом:

let tom: Person = Person(name:"Tom", age: 23)
let bob: Person = Employee(name: "Bob", age: 28, company: "Apple")
let alice: Person = Manager(name: "Alice", age: 31, company: "Microsoft")


Все три константы представляют тип Person, однако первая хранит ссылку на объект Person, вторая - на объект Employee, а третья - на объект Manager. Таким образом, переменная или константа одного типа может принимать многообразные формы в зависимости от конкретного объекта, на который она указывает.

Но что будет, если мы вызовем метод display() для всех трех объектов:

let tom: Person = Person(name:"Tom", age: 23)
let bob: Person = Employee(name: "Bob", age: 28, company: "Apple")
let alice: Person = Manager(name: "Alice", age: 31, company: "Microsoft")

tom.display() // Имя: Tom Возраст: 23
bob.display() // Имя: Bob Возраст: 28 Сотрудник компании: Apple
alice.display() // Имя: Alice Возраст: 31 Менеджер компании: Microsoft


Несмотря на то, что все три константы представляют тип Person, при вызове метода display будет вызываться реализация метода именно того класса, ссылку на объект которого хранит константа. Данный примем называется динамической диспетчеризацией - во время выполнения программы на основании типа объекта система решает, какую именно реализацию метода вызывать.

В одной стороны, это нам дает ряд преимуществ - мы можем работать с объектом производного типа как с объектом базового типа и использовать его везде, где требуется объект базового типа. Но с другой стороны, поскольку решение о выборе реализации принимается во время выполнения, то это несколько замедляет общий ход работы программы.

🐸 Библиотека мобильного разработчика

#буст #JuniorKit #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM
5🥱1