LibCode
Темы
5 ответов
6 ответов
1 ответ
3 ответа
В современном мире Android-разработки мы оказались на перекрестке двух разных философий создания интерфейса. С одной стороны — проверенный временем XML, с другой — стремительно набирающий популярность декларативный подход (Jetpack Compose). Что выбрать в 2024 году? Давайте разберем плюсы и минусы обоих методов.
Долгое время XML был единственным стандартом. Вы описываете структуру экрана в одном файле, а затем «оживляете» её в Kotlin/Java коде через findViewById или View Binding.
Плюсы:
Минусы:
Декларативный подход меняет парадигму. Вы не говорите системе, как изменить интерфейс (например, «сделай текст красным»), вы описываете, каким интерфейс должен быть при определенном состоянии.
Плюсы:
Классический путь: XML-макеты (Imperative UI)
Долгое время XML был единственным стандартом. Вы описываете структуру экрана в одном файле, а затем «оживляете» её в Kotlin/Java коде через findViewById или View Binding.
Плюсы:
- Разделение ответственности: Дизайн (XML) отделен от логики (Kotlin).
- Предсказуемость: Огромное количество документации, библиотек и готовых решений.
- Визуальный редактор: Возможность видеть структуру дерева элементов в режиме Preview.
Минусы:
- Избыточность: Чтобы изменить одну кнопку, вам нужно создать XML, найти её в коде, привязать слушатель и обновить состояние.
- Сложность иерархии: Глубокая вложенность
LinearLayoutиRelativeLayoutчасто приводит к падению производительности. - «Разрыв» контекста: Вам приходится постоянно переключаться между двумя файлами.
Новый путь: Декларативный подход (Jetpack Compose)
Декларативный подход меняет парадигму. Вы не говорите системе, как изменить интерфейс (например, «сделай текст красным»), вы описываете, каким интерфейс должен быть при определенном состоянии.
Плюсы:
- Скорость разработки: Кода становится в разы меньше. Весь UI пишется на одном языке — Kotlin.
- Реактивность: Интерфейс автоматически обновляется при изменении
- Жалоба
В Jetpack Compate интерфейс строится не из XML-файлов, а из функций Kotlin. Чтобы управлять положением кнопок, текстов и картинок, используются специальные контейнеры — Composable-функции, которые диктуют правила расположения своих «детей».
Разберем всю иерархию: от базовых строительных блоков до мощных инструментов для сложных экранов.
Располагает элементы друг под другом, сверху вниз. Это прямой аналог
Для чего нужен:
Пример:
Располагает элементы слева направо. Аналог
Для чего нужен:
Пример:
...
Разберем всю иерархию: от базовых строительных блоков до мощных инструментов для сложных экранов.
Column() — Вертикальный стек
Располагает элементы друг под другом, сверху вниз. Это прямой аналог
LinearLayout(vertical).Для чего нужен:
- Формы регистрации и экраны настроек.
- Ленты постов, где каждый элемент идет за другим.
- Группировка заголовка, текста и кнопки.
Пример:
ВыделитьKotlin
@Composable
fun UserProfile() {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally // Центрируем всё по ширине
) {
Text(text = "Алексей", style = MaterialTheme.typography.headlineSmall)
Text(text = "@prizrak_dev", color = Color.Gray)
Spacer(modifier = Modifier.height(12.dp)) // Пустое пространство
Button(onClick = { /* ... */ }) { Text("Написать") }
}
}Row() — Горизонтальный стек
Располагает элементы слева направо. Аналог
LinearLayout(horizontal).Для чего нужен:
- Элементы меню (иконка + текст).
- Панели инструментов (Toolbar).
- Кнопки, где текст и иконка стоят в ряд.
Пример:
ВыделитьKotlin
@Composable
fun MessageItem() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment =
Если вы когда-нибудь писали мессенджер или любое приложение, которому нужно сообщать пользователю о новых событиях, вы рано или поздно сталкиваетесь с тем, что уведомления в Android — это не «показать текст в шторке», а целая система с собственными правилами. В этой статье я разберу, как уведомления устроены внутри, почему идентификация пушей — это отдельная инженерная задача, и какие решения здесь работают надёжнее всего. Материал не привязан к конкретному проекту, но примеры будут на Kotlin.
Каждое уведомление в шторке — это объект с двумя ключами: тегом (строка, необязательная) и ID (целое число, обязательное). Именно пара «тег — ID» определяет уникальность. Это значит, что два уведомления с одинаковым ID, но разными тегами, спокойно сосуществуют, а два уведомления с одинаковой парой заменяют друг друга.
Это первое место, где начинающие разработчики теряются. Типичный сценарий: приложение шлёт уведомления о новых сообщениях, и разработчик генерирует для каждого сообщения случайный ID, а потом... не может его удалить, потому что ID нигде не сохранён. Классическое решение — заводить карту соответствий «чат → ID уведомления» и хранить её в базе или в SharedPreferences. Решение рабочее, но у него есть фундаментальный недостаток: оно вводит вторую систему учёта. Данные теперь живут в двух местах — в системе уведомлений и в вашем хранилище. И рано или поздно они расходятся: приложение упало между сохранением и показом, пользователь очистил данные...
Атомарность уведомления
Каждое уведомление в шторке — это объект с двумя ключами: тегом (строка, необязательная) и ID (целое число, обязательное). Именно пара «тег — ID» определяет уникальность. Это значит, что два уведомления с одинаковым ID, но разными тегами, спокойно сосуществуют, а два уведомления с одинаковой парой заменяют друг друга.
Это первое место, где начинающие разработчики теряются. Типичный сценарий: приложение шлёт уведомления о новых сообщениях, и разработчик генерирует для каждого сообщения случайный ID, а потом... не может его удалить, потому что ID нигде не сохранён. Классическое решение — заводить карту соответствий «чат → ID уведомления» и хранить её в базе или в SharedPreferences. Решение рабочее, но у него есть фундаментальный недостаток: оно вводит вторую систему учёта. Данные теперь живут в двух местах — в системе уведомлений и в вашем хранилище. И рано или поздно они расходятся: приложение упало между сохранением и показом, пользователь очистил данные...
Для большинства разработчиков написание кода на PHP выглядит магически просто. Вы создаете файл "index.php", пишете там пару строк, нажимаете «Обновить» в браузере — и результат перед глазами.
Эта простота обманчива. Она создает иллюзию того, что PHP — это медленный интерпретатор, который читает ваш код сверху вниз, как стихотворение, спотыкаясь о каждую запятую. На самом деле современный PHP (версии 7 и 8) устроен гораздо сложнее и ближе к таким языкам, как Java или C#, чем к старым скриптовым технологиям.
Давайте разберем путь вашего кода от нажатия клавиши Enter до появления пикселей на мониторе пользователя.
Когда сервер получает запрос к .php файлу, управление берет Zend Engine — сердце языка. Но процессор компьютера не понимает команды типа
На выходе получается поток данных, где вместо букв стоят их числовые идентификаторы.
Поток токенов еще бесполезен. Нужно понять логику. Этим занимается парсер. Он собирает из кирпичиков структуру — Abstract Syntax Tree (AST). Представьте это как схему предложения. Парсер понимает, что
Эта простота обманчива. Она создает иллюзию того, что PHP — это медленный интерпретатор, который читает ваш код сверху вниз, как стихотворение, спотыкаясь о каждую запятую. На самом деле современный PHP (версии 7 и 8) устроен гораздо сложнее и ближе к таким языкам, как Java или C#, чем к старым скриптовым технологиям.
Давайте разберем путь вашего кода от нажатия клавиши Enter до появления пикселей на мониторе пользователя.
Этап 1. Лексический анализ (Токенизация)
Когда сервер получает запрос к .php файлу, управление берет Zend Engine — сердце языка. Но процессор компьютера не понимает команды типа
$user = 'Alex';. Сначала работает лексер (Lexer, утилита re2c). Он сканирует ваш текстовый файл и разбивает его на токены — элементарные кирпичики языка:
- Открывающий тег
<?php. - Переменная
$user. - Оператор присваивания
=. - Строка
'Alex'. - Точка с запятой
;.
На выходе получается поток данных, где вместо букв стоят их числовые идентификаторы.
Этап 2. Парсинг и Абстрактное синтаксическое дерево (AST)
Поток токенов еще бесполезен. Нужно понять логику. Этим занимается парсер. Он собирает из кирпичиков структуру — Abstract Syntax Tree (AST). Представьте это как схему предложения. Парсер понимает, что
$user — это субъект, а 'Alex' — объект, и между ними есть связь присвоения. Если вы допустили синтаксическую...
В старых PHP-проектах (Особенно в движках форумов и CMS, написанных в 2010‑х) часто встречается однотипный шаблон: обработка BBCode через
Такой код рабочий, но визуально тяжёлый и избыточный. Современный PHP (начиная с версии 7.4) позволяет сделать его чище, лаконичнее и удобнее в поддержке — за счёт стрелочных функций.
Типичный старый вариант выглядит так:
Здесь три «лишних» элемента:
Это не ошибка, но в проектах с десятками подобных обработчиков такой стиль делает код громоздким.
Начиная с PHP 7.4, можно использовать стрелочную функцию:
Что изменилось:
Код стал короче и понятнее.
Случай 1: Простой обработчик с $this
Было:
...
preg_replace_callback с обычными анонимными функциями, где для доступа к $this приходится использовать конструкцию use($this).Такой код рабочий, но визуально тяжёлый и избыточный. Современный PHP (начиная с версии 7.4) позволяет сделать его чище, лаконичнее и удобнее в поддержке — за счёт стрелочных функций.
Что было раньше: Классический синтаксис
Типичный старый вариант выглядит так:
ВыделитьPHP
$text = preg_replace_callback(
'/\[url(.*?)\](.*?)\[\/url\]/is',
function($match) use ($this) {
return $this->_link($match[1], $match[2]);
},
$text);Здесь три «лишних» элемента:
- Ключевое слово
function— многословно. - Явное указание
use ($this)— избыточно, потому что$thisнужен почти всегда. - Фигурные скобки и оператор
return— занимают строки, не добавляя смысла.
Это не ошибка, но в проектах с десятками подобных обработчиков такой стиль делает код громоздким.
Современный вариант: Стрелочные функции
Начиная с PHP 7.4, можно использовать стрелочную функцию:
ВыделитьPHP
$text = preg_replace_callback(
'/\[url(.*?)\](.*?)\[\/url\]/is',
fn($m) => $this->_link($m[1], $m[2]),
$text);Что изменилось:
fn($m)вместоfunction($match).- Нет
use ($this):$thisавтоматически доступен внутри стрелочной функции. - Одно выражение после
=>заменяетreturn.
Код стал короче и понятнее.
Переход на fn в типовых случаях
Случай 1: Простой обработчик с $this
Было:
...
При написании приложения может потребоваться проверить данные, как они были обработаны. Для этого, за частую, достаточно их просто увидеть. В этом нам поможет класс Gson. Чтобы его использовать, необходимо импортировать:
Если, по каким то причинам его у вас до сих пор нет, добавляем в "build.gradle.kts":
После чего, если потребуется, пересоберите проект. И выводите нужные данные так:
В лог выводим так:
Импорт логирования:
В логе увидите строку с данными.
ВыделитьKotlin
import android.util.LogЕсли, по каким то причинам его у вас до сих пор нет, добавляем в "build.gradle.kts":
ВыделитьKotlin
implementation("com.google.code.gson:gson:xx.xx.xx")xx.xx.xx: Указывайте актуальную версию.
После чего, если потребуется, пересоберите проект. И выводите нужные данные так:
ВыделитьKotlin
Gson().toJson(data)В лог выводим так:
ВыделитьKotlin
Log.d("Data", Gson().toJson(data))Импорт логирования:
ВыделитьKotlin
import android.util.LogВ логе увидите строку с данными.
В Jetpack Compose есть несколько стандартных индикаторов загрузки:
Примеры их использования:
Дополнительные возможности настройки:
Можно также комбинировать индикаторы с другими компонентами, например:
Все эти компоненты автоматически анимируются и адаптируются под тему приложения.
CircularProgressIndicator: Классический круговой индикатор.LinearProgressIndicator: Линейный индикатор.ActivityIndicator: Простой вращающийся индикатор.
Примеры их использования:
ВыделитьKotlin
// Круговой индикатор
CircularProgressIndicator(
modifier = Modifier
.size(40.dp)
.padding(16.dp),
color = MaterialTheme.colorScheme.primary
)
// Линейный индикатор
LinearProgressIndicator(
progress = 0.5f, // значение от 0 до 1
modifier = Modifier
.height(4.dp)
.fillMaxWidth()
.padding(16.dp)
)
// Простой индикатор активности
ActivityIndicator(
modifier = Modifier
.size(40.dp)
.padding(16.dp),
color = MaterialTheme.colorScheme.primary
)Дополнительные возможности настройки:
- Изменение размера через модификатор
size(). - Настройка цвета через параметр
color. - Анимация прогресса для
LinearProgressIndicator. - Стилизация через темы Material Design.
Можно также комбинировать индикаторы с другими компонентами, например:
ВыделитьKotlin
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Загрузка данных...")
Spacer(modifier = Modifier.height(16.dp))
CircularProgressIndicator()
}Все эти компоненты автоматически анимируются и адаптируются под тему приложения.
В Kotlin можно получить часовой пояс пользователя несколькими способами.
Получение названия часового пояса
Получение смещения от UTC
...
Через TimeZone
ВыделитьKotlin
val timeZone = TimeZone.getDefault()
val timeZoneId = timeZone.id // например: "Europe/Moscow"
val offset = timeZone.rawOffset / (1000 * 60 * 60) // смещение в часахЧерез ZoneId (Рекомендуемый способ)
ВыделитьKotlin
import java.time.ZoneId
val zoneId = ZoneId.systemDefault()
val zoneIdString = zoneId.toString() // например: "Europe/Moscow"Через Calendar
ВыделитьKotlin
import java.util.Calendar
val calendar = Calendar.getInstance()
val timeZone = calendar.timeZone
val offset = timeZone.getRawOffset()Через LocalDateTime
import java.time.LocalDateTime
import java.time.ZoneId
val currentTime = LocalDateTime.now(ZoneId.systemDefault())Примеры использования
Получение названия часового пояса
ВыделитьKotlin
fun getUserTimeZone(): String {
return ZoneId.systemDefault().toString()
}Получение смещения от UTC
ВыделитьKotlin
fun getTimeZoneOffset(): Int {
val zone = TimeZone.getDefault()
return zone.getRawOffset() / (1000 * 60 * 60)
}Важные моменты
- Часовой пояс определяется автоматически на основе настроек устройства.
- Методы возвращают:
- Идентификатор часового пояса (например: “Europe/Moscow”).
- Смещение от UTC в часах.
- Информацию о переходе на летнее время.
Пример полного использования
ВыделитьKotlin
fun main() {
val zoneId = ZoneId.systemDefault()
println("Часовой пояс: $zoneId")
val timeZone = TimeZone.getDefault()
Функция println() является стандартной конструкцией в языке Kotlin, позволяющей выводить значения и выражения в консольный поток вывода. Ее название расшифровывается как «
В этой статье мы детально рассмотрим особенности использования функции
Основная форма записи функции
Параметр
Пример:
Результатом выполнения будет вывод в консоли следующей строки:
1. Вывод чисел и арифметических выражений:
2. Вывод строки с конкатенацией значений:
...
print line» («напечатать строку»), подразумевая, что после каждого вызова происходит автоматический перенос каретки на новую строку.В этой статье мы детально рассмотрим особенности использования функции
println(), ее синтаксис, варианты применения и лучшие практики.Синтаксис и базовые примеры
Основная форма записи функции
println() следующая:
ВыделитьKotlin
println([value])Параметр
[value] необязателен и может содержать любое выражение или литеральное значение. Если аргумент отсутствует, то просто выводится новая строка.Пример:
ВыделитьKotlin
println("Hello, World!")Результатом выполнения будет вывод в консоли следующей строки:
Hello, World!
Особенности функции println()
- Автоматический перенос строки: После каждого вызова
println()происходит переход на новую строку. - Возможность вывода любых выражений: Можно передавать в качестве аргументов числа, строки, объекты, результаты вычислений и другие типы данных.
- Строковое представление объектов: Если передается объект, он автоматически преобразуется в строку посредством вызова метода
toString().
Несколько примеров использования
1. Вывод чисел и арифметических выражений:
ВыделитьKotlin
val a = 10
val b = 20
println(a + b) // Выведет число 302. Вывод строки с конкатенацией значений:
ВыделитьKotlin
val firstName = "John"
val lastName = "Doe"
println("$firstName
В Jetpack Compose есть несколько способов уменьшить расстояние между строками в тексте:
Через параметр lineSpacing в TextStyle:
Через lineHeight в TextStyle:
Для Column (если речь про расстояние между элементами):
Для LazyColumn или LazyRow:
Для нескольких Text в Column:
Важные моменты:
Вы можете комбинировать эти методы в зависимости от конкретной задачи и структуры вашего интерфейса.
Через параметр lineSpacing в TextStyle:
ВыделитьKotlin
Text(
text = "Ваш текст",
style = TextStyle(
lineSpacing = 1.2f // значение меньше 1 уменьшит расстояние
)
)Через lineHeight в TextStyle:
ВыделитьKotlin
Text(
text = "Ваш текст",
style = TextStyle(
lineHeight = 20.sp // задайте конкретное значение в sp
)
)Для Column (если речь про расстояние между элементами):
ВыделитьKotlin
Column(
verticalArrangement = Arrangement.spacedBy(8.dp) // задайте нужное расстояние
) {
Text("Строка 1")
Text("Строка 2")
}Для LazyColumn или LazyRow:
ВыделитьKotlin
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(itemsList) { item ->
Text(item.text)
}
}Для нескольких Text в Column:
ВыделитьKotlin
Column {
Text("Первая строка")
Spacer(modifier = Modifier.height(4.dp)) // задайте нужное значение
Text("Вторая строка")
}Важные моменты:
- lineSpacing — относительное значение (коэффициент)
- lineHeight — абсолютное значение в sp
- spacedBy — для расстояний между элементами в Column/Row
- Spacer — для ручного задания отступов
Вы можете комбинировать эти методы в зависимости от конкретной задачи и структуры вашего интерфейса.
Разработчики Android нередко сталкиваются с путаницей относительно использования различных версий класса
Класс R представляет собой специальную структуру, автоматически создаваемую Android Studio во время процесса сборки проекта. Он служит контейнером ссылок на ресурсы вашего приложения (изображения, цвета, стили, макеты и т.д.). Каждый ресурс автоматически получает уникальный числовой идентификатор, хранящийся в соответствующих полях класса
Классы R бывают двух типов:
Что это?Это главный класс ресурсов вашего собственного приложения. Здесь хранятся идентификаторы всех ресурсов, размещённых в директории
Как используется?
Используя класс
Что это?Библиотека Material...
R, особенно когда речь идет о доступе к ресурсам своего приложения и сторонних библиотек. Эта статья поможет разобраться в разнице между app.R и com.google.android.material.R, а также научит правильно обращаться к нужным ресурсам.Что такое классы R?
Класс R представляет собой специальную структуру, автоматически создаваемую Android Studio во время процесса сборки проекта. Он служит контейнером ссылок на ресурсы вашего приложения (изображения, цвета, стили, макеты и т.д.). Каждый ресурс автоматически получает уникальный числовой идентификатор, хранящийся в соответствующих полях класса
R.Классы R бывают двух типов:
- Ваш собственный класс
R, называемыйapp.R, соответствует ресурсам вашего проекта. - Класс R сторонней библиотеки, обозначаемый именем пакета библиотеки (например,
com.google.android.material.R), содержит ссылки на ресурсы конкретной внешней библиотеки.
app.R
Что это?Это главный класс ресурсов вашего собственного приложения. Здесь хранятся идентификаторы всех ресурсов, размещённых в директории
/res/ вашего проекта. Все строки, цвета, размеры, рисунки и прочее хранится именно тут.Как используется?
Используя класс
app.R, вы обращаетесь к собственным ресурсам приложения:
ВыделитьKotlin
setBackgroundColor(ContextCompat.getColor(context, R.color.my_color))
setImageResource(R.drawable.ic_launcher_background)com.google.android.material.R
Что это?Библиотека Material...
При разработке приложений на Android с использованием Jetpack Compose и стандартных подходов на основе Activity/Fragments вы можете столкнуться с двумя разными способами получения строковых ресурсов: функцией
stringResource()
Пример использования:
getString()
Пример использования:
...
stringResource() и методом getString(). Оба способа служат схожей цели — получают строку из ресурсов проекта, однако имеют разное назначение и области применения.Функционал и различия
stringResource()
- Назначение: Используется в рамках композиции Composable-функций Jetpack Compose.
- Где применяется: Только внутри функций с аннотацией
@Composable. - Формат: Выглядит как простая функция, принимающая ID ресурса:
stringResource(R.string.your_string_id). - Импорт: Необходимо подключить следующий импорт:
ВыделитьKotlin
import androidx.compose.ui.res.stringResource
Пример использования:
ВыделитьKotlin
@Composable
fun MyScreen() {
Text(text = stringResource(R.string.welcome_message))
}getString()
- Назначение: Применяется вне контекста Compose-композиций, например, в обычных Activity, Fragment или ViewModels.
- Где применяется: Внутри классов наследников от Context, AppCompatActivity, Fragments и других компонент, поддерживающих ресурсный контекст.
- Формат: Методы
getString()относятся непосредственно к объекту контекста, например:context.getString(R.string.your_string_id)или activity.getString(R.string.your_string_id). - Импорт: Стандартный импорт ресурсов не нужен, поскольку метод доступен напрямую в классе Context и производных от него объектах.
Пример использования:
ВыделитьKotlin
class
В Kotlin (особенно при работе с корутинами и асинхронными операциями) часто встречаются конструкции onSuccess и onError. Они служат для обработки результатов асинхронных операций — как успешных, так и ошибочных.
Это лямбда‑функции (callback-обработчики), которые:
Они позволяют отделить логику обработки результата от самого выполнения операции.
Чаще всего встречаются в:
Базовый синтаксис
Сетевой запрос (Retrofit)
Корутины с async
...
Что это такое?
Это лямбда‑функции (callback-обработчики), которые:
onSuccess— вызывается при успешном завершении операции;onError— вызывается при возникновении ошибки.
Они позволяют отделить логику обработки результата от самого выполнения операции.
Где применяются
Чаще всего встречаются в:
- сетевых запросах (Retrofit, Ktor);
- работе с БД (Room, SQLDelight);
- корутинах (async, await);
- RxJava/RxKotlin;
- библиотеках для работы с файлами/IO.
Базовый синтаксис
ВыделитьKotlin
someAsyncOperation(
onSuccess = { result ->
// Обработка успешного результата
},
onError = { exception ->
// Обработка ошибки
}
)Практические примеры
Сетевой запрос (Retrofit)
ВыделитьKotlin
apiService.getUser(userId)
.enqueue(object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
if (response.isSuccessful) {
onSuccess(response.body()!!)
} else {
onError(HttpException(response))
}
}
override fun onFailure(call: Call<User>, t: Throwable) {
onError(t)
}
})Корутины с async
ВыделитьKotlin
scope.launch {
val deferred = async {
fetchDataFromNetwork()
}
try {
val result =
