Библиотека мобильного разработчика | Android, iOS, Swift, Retrofit, Moshi, Chuck
9.56K 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
Какой тип находится на вершине иерархии типов в 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
Тень под карточкой в Jetpack Compose

Красивые мягкие тени делают интерфейс объёмным и аккуратным. В Compose этого можно добиться с помощью drawBehind, чтобы контролировать цвет, размытие и смещение тени — как в дизайне.

💡Реализация

@Composable
fun ShadowCard(
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit
) {
Box(
modifier = modifier
.drawBehind {
drawRoundRect(
color = Color(0x1A000000), // мягкая полупрозрачная тень
cornerRadius = CornerRadius(16.dp.toPx()),
topLeft = Offset(0f, 6.dp.toPx())
)
}
.background(Color.White, RoundedCornerShape(16.dp))
.padding(16.dp)
) {
content()
}
}


Тень выглядит естественно, без резких границ, повторяет форму карточки и не «съезжает» на светлой теме.

📌 Применение

ShadowCard(
modifier = Modifier
.fillMaxWidth()
.height(120.dp)
) {
Text("PixelPerfect ", modifier = Modifier.align(Alignment.Center))
}


Такой подход легко адаптировать под любые формы и цвета. Главное — держать параметры тени (смещение, прозрачность, радиус) в синхронизации с макетами из Figma.

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

#PixelPerfect #JuniorKit #Android
Please open Telegram to view this post
VIEW IN TELEGRAM
Как функции расширения работают под капотом

По своей задумке, функция расширения Kotlin — это дополнительный метод для любого объекта, даже для потенциально несуществующего (нуллабельного). Этот инструмент является прямой реализацией переопределения методов паттерна проектирования Декоратор.

ВАЖНО: В функциях расширения мы можем обращаться к любым общедоступным свойствам и методам объекта, однако не можем обращаться к свойствам и методам с модификаторами private и protected.

ВАЖНО: Функции расширения не переопределяют функции, которые уже определены в классе. Если функция расширения имеет ту же сигнатуру, что и уже имеющаяся функция класса, то компилятор просто будет игнорировать подобную функцию расширения.

Определение аналогично определению обычной функции за тем исключением, что после слова fun идет название типа, для которого определяется функция, и через точку название функции. Определим пару функций расширения к типам Int и String:

fun main() {

val hello: String = "hello world"
println(hello.wordCount('l')) // 3
println(hello.wordCount('o')) // 2
println(4.square()) // 16
println(6.square()) // 36
}

fun String.wordCount(c: Char) : Int {
var count = 0
for(n in this) {
if(n == c) count++
}
return count
}
fun Int.square(): Int {
return this * this
}


Для типа Int определена функция возведения в квадрат. В каждой функции расширения через ключевое слово this мы можем ссылаться на текущий объект того типа, для которого создается функция. Например, в функции:

fun Int.square(): Int {
return this * this
}


Через this обращаемся к тому объекту, для которого будет вызываться функция. И затем вы можем вызвать ее следующим образом:

4.square()      // 16


Для типа String определена функция wordCount, которая подсчитывает, сколько встречается определенный символ в строке.

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

#буст #JuniorKit #Kotlin
Please open Telegram to view this post
VIEW IN TELEGRAM