Academy Minsk News & Announcements


Channel's geo and language: Belarus, Russian
Category: Technologies


Самые интересные новости, видео с конференций из Android мира. Анонсы митапов, важных и полезных конференций от сообщества Android Academy Minsk.
Обсудить материал вы можете в чате: https://t.me/androidacademyminsk

Related channels  |  Similar channels

Channel's geo and language
Belarus, Russian
Statistics
Posts filter


Why you should never use !! in Kotlin

В данной статье автор поясняет почему не стоит использовать !! оператор в Kotlin.

Хоть автор говорит, что !! – это порочная практика, я не могу согласиться, потому как надо в первую очередь учитывать контекст как со стороны бизнеса так и со стороны технической части. Мне не очень нравится, когда говорят, что та или иная практикая плохая. Каждая практика может быть применена в зависимости от контекста. Есть трэйд офф и нужно четко оценивать ситуацию, в которой вы сейчас находитесь, и от этого и отталкиваться при принятии решений.


Best Practices for Composition Patterns in Jetpack Compose

В данной статье автор рассматривает два подхода организации UI с Compose: Slot pattern и Compound Component pattern. Если вы с этим незнакомы, то будет полезно через примеры автора узнать детали.

🔸 Предположим, что вы работает над карточкой пользователя. Есть две вариации: публичная и персональные карточки. Персональный профиль в отличие от публичного профиля, должен быть редактируемым и доступным для шаринга
🔸 Самое простое решение — использовать условные операторы, чтобы иметь один компонент для отрисовки каточки:
@Composable
fun UserProfile(
user: User,
isSelf: Boolean,
) {
Column(...) {
Row(...) {
ProfileImage(...)
Name(...)
}
Bio(...)
if (isSelf) {
Toolbox(...)
}
}
}

🔸 Однако добавление разветвлений только усложняет всю логику. По мере роста компонента появляется возможность добавления гораздо большего количества условных операторов:
@Composable
fun UserProfile(...) {
Column(...) {
...
// “If-else hell” causes strong coupling
if (isSelf) { ... }
if (isPremiumMember) { ... }
if (shouldShowEmail) { ... }
else {...}
...
}
}

🔸 В принципе DRY дублирование оценивается не по внешнему виду кода, а по тому, является ли причина изменения кода той же самой
🔸 Добавление условных операторов в компоненты означает, что компоненты с разными причинами модификации объединяются вместе, и эта логика не подходит для повторного использования
🔸 Это противоречит принципам объектно-ориентированного проектирования, в частности принципу единой ответственности. Это происходит потому, что компонент UserProfile не только отображает информацию профиля, но и обрабатывает логику пользовательского интерфейса на основе разных условий, что добавляет слишком много обязанностей к одному компоненту
Решения:
1️⃣ Slot pattern
🔸 Один из способов ослабить тесную связь между компонентами и обеспечить более гибкую композицию компонентов пользовательского интерфейса
🔸 Компонуемые лямбды (в форме @Composable () -> Unit) могут использоваться в качестве параметров функции в compose, позволяя родительскому компоненту определять структуру пользовательского интерфейса вне зависимости от конкретной реализации дочернего компонента:
@Composable
fun UserProfile(
user: User,
// Receive the UI itself as a parameter via composable lambda (Slot)
bottomContent: @Composable () -> Unit,
) {
Column(...) {
...
// The UserProfile component does not know any incoming UI components
bottomContent(...)
}
}

🔸 В итоге публичная и персональная карточки можно представить так:
@Composable
fun SelfProfile(
user: User
) {
UserProfile(user) {
Toolbox(...)
}
}

@Composable
fun PublicProfile(
user: User
) {
UserProfile(user)
}

🔸 Material Design Components в основном используют Slot подход:
@Composable
fun CenterAlignedTopAppBar(
title: @Composable () -> Unit,
navigationIcon: @Composable () -> Unit = {},
actions: @Composable RowScope.() -> Unit = {},
...

🔸 Такой подход полезен для корректировки содержимого определенной области без изменения структуры макета компонента
🔸 С таким подходом тоже есть сложности. К примеру, если вдруг в какую-то часть UI надо будет добавить доп информацию, то стоит тогда UI разбивать на верх и низ и т.д. что делает логику тоже сложнее:
@Composable
fun UserProfile(
user: User,
centerContent: @Composable () -> Unit,
bottomContent: @Composable () -> Unit,
// and so on…
) { ... }

Далее автор знакомит нас с Compound Component pattern. Если вам интересно, то предлагаю дочитать до конца


Mastering kotlinx.serialization: Advanced Techniques and Tricks

В данной статье автор собрал набор рекомендаций как можно использовать kotlinx.serialization в проекте.

🔸 Можно с легкостью создать свой кастомный сериализатор. Когда стоит создавать свой:
📌 Нестандартные форматы данных
📌 Оптимизация производительности
📌 Полиморфная сериализация
private fun String.camelcase(): String {
val words = lowercase().split("_")
return words[0] + words.drop(1).joinToString("") { word ->
word.replaceFirstChar { it.titlecase(Locale.ENGLISH) }
}
}

open class CamelcaseSerializer(private val values: EnumEntries) : KSerializer where T : Enum {
override val descriptor = PrimitiveSerialDescriptor("CamelcaseSerializer", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): T {
val value = decoder.decodeString()
return values.first { it.name.camelcase() == value }
}

override fun serialize(encoder: Encoder, value: T) = encoder.encodeString(value.name.camelcase())
}

🔸 Часто сталкиваемся с задачей парсинга enum-ов. К примеру, автор приводит пример как можно сериализовать значения перечисления в строки с заглавными буквами:
open class UppercaseSerializer(private val values: EnumEntries) : KSerializer where T : Enum {
override val descriptor = PrimitiveSerialDescriptor("UppercaseSerializer", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): T {
val value = decoder.decodeString()
return values.first { it.name.uppercase() == value }
}

override fun serialize(encoder: Encoder, value: T) = encoder.encodeString(value.name.uppercase())
}

🔸 И дальше его использовать в коде:
@Serializable(with = Status.StatusSerializer::class)
enum class Status {
ACTIVE,
INACTIVE,
PENDING;

data object StatusSerializer : UppercaseSerializer(entries)
}

@Serializable
data class User(
val status: Status
)

val user = User(Status.ACTIVE)
val json = Json.encodeToString(user)
println(json)

🔸 InlineSerializer имитирует процесс сериализации класса значений без ограничений реальных встроенных классов. Он позволяет вам сериализовать свойство напрямую, не оборачивая его в класс:
open class InlineSerializer(
private val kSerializer: KSerializer,
private val property: KProperty1,
) : KSerializer {
override val descriptor = NbtTag.serializer().descriptor

override fun deserialize(decoder: Decoder) = error("InlineSerializer cannot be deserialized")

override fun serialize(encoder: Encoder, value: T) = encoder.encodeSerializableValue(kSerializer, property.get(value))
}

🔸 И использование такое:
@Serializable(with = Storage.StorageSerializer::class)
data class Storage(
val list: List = emptyList(),
) {
data object StorageSerializer : InlineSerializer(String.serializer(), Storage::id)
}

val storage = Storage(listOf("item1", "item2"))
val json = Json.encodeToString(sound)
println(json)

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


Debugging Kotlin Coroutines: Making “Optimised Out” Variables Visible

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

Надо добавить аргумент для компилятора:
kotlin {
compilerOptions {
if (System.getProperty("idea.active") == "true") {
println("Enable coroutine debugging")
freeCompilerArgs.add("-Xdebug")
}
}
}


Naming is Hard

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

ART организует все поля в памяти в алфавитном порядке. Оказывается способ, которым ART упорядочивает поля, немного сложнее, чем просто сортировка их по алфавиту:
📌 Поля группируются по типу (Long, Float и т. д.)
📌 Большие типы идут первыми (Double/Long, затем Float/Int и т. д.)
📌 Поля сортируются по алфавиту внутри группы
📌 Некоторые поля могут нарушать указанный выше порядок из-за заполнения/выравнивания

В итоге такие вещи могут приводит к улучшениям производительности, что автор показал проведя benchmarks.


kotlinx.coroutines 1,10.0

Вышла новая версия корутин. Основные изменения:
🔸 Обновиили Kotlin до версии 2.1.0
🔸 Добавили Flow.any, Flow.all и Flow.none
🔸 Реорганизован код kotlinx-coroutines-debug и kotlinx-coroutines-core, чтобы избежать разделения пакета между двумя артефактами
🔸 Исправлен NullPointerException при использовании десериализованных Java исключений kotlinx-coroutines-core
🔸 Исправлена ​​ошибка, которая задерживала планирование задачи Dispatchers.Default или Dispatchers.IO после yield() в редких сценариях
🔸 Исправлена ​​ошибка, которая не позволяла main() корутине в Wasm/WASI выполняться после вызова delay() в некоторых сценариях
🔸 Исправлено планирование runBlocking задач в Kotlin/Native
🔸 Исправлены некоторые терминальные операторы Flow, которые иногда возобновлялись без учета отмены


The Second Developer Preview of Android 16

Вышла вторая версия превью Android 16. Что о ней известно:
🔸 ProfilingManager был добавлен в Android 15. Чтобы помочь со сложными сценариями трассировки, такие как запуски или ANR, ProfilingManager теперь включает System Triggered Profiling. Приложения могут использовать ProfilingManager#addProfilingTriggers() для регистрации к получению информации об этих потоках:
val anrTrigger = ProfilingTrigger.Builder(
ProfilingTrigger.TRIGGER_TYPE_ANR
)
.setRateLimitingPeriodHours(1)
.build()

val startupTrigger: ProfilingTrigger = //...

mProfilingManager.addProfilingTriggers(listOf(anrTrigger, startupTrigger))

🔸 ApplicationStartInfo был добавлен в Android 15. Android 16 добавляет getStartComponent(), чтобы различать, какой тип компонента вызвал запуск
🔸 В Android 16 добавили haptic APIs
🔸 В Android 16 добавлен JobScheduler#getPendingJobReasons(int jobId), который может возвращать несколько причин, по которым джоб находится в состоянии ожидания, как из-за явных ограничений, установленных разработчиком, так и из-за неявных ограничений, установленных системой
🔸 Добавили JobScheduler#getPendingJobReasonsHistory(int jobId), который возвращает список последних изменений ограничений
🔸 В Android 16 DP2 добавлены hasArrSupport() и getSuggestedFrameRate(int) при восстановлении getSupportedRefreshRates(), чтобы приложениям было проще использовать преимущества ARR
🔸 Начиная с Android 16, корректируют квоту времени выполнения обычного и ускоренного выполнения джобов на основе следующих факторов:
📌 В каком app standby bucket приложения находится приложение
📌 Джоба, запущенные, когда приложение видимо пользователю, и продолжающиеся после того, как приложение становится невидимым, будут придерживаться квоты времени выполнения джобы
📌 Джобы, которые выполняются одновременно с активным сервисом, будут придерживаться квоты времени выполнения джобы

🔸 Метод JobInfo#setImportantWhileForeground полностью задепрекейтили и вызов его – не будет ничего делать

И еще несколько вещей, которые показались не так супер интересными.


Get your apps ready for 16 KB page size devices

Android развивается и одним из ключевых улучшений является изменение размера страницы памяти 16 КБ. Это изменение позволяет системе эффективнее управлять памятью, что приводит к заметному повышению производительности (5–10%) как в приложениях, так и в играх.

Чтобы протестировать приложение на устройствах с памятью 16 КБ, эта фича доступна в качестве опции разработчика на устройствах Google Pixel 8 и 9.

Чтобы приложение работало на устройствах с размером страницы 16 КБ, нужно выполнить следующие действия:
1️⃣ Обновите инструменты: Android Gradle Plugin (AGP) 8.5.1 или выше
2️⃣ Align your native code: если приложение включает нативный код, используйте NDK версии r28 или выше
3️⃣ Обновите SDK и библиотеки


Coffee&Code: Kotlin Coroutines 🤗

Напоминалочка, что сегодня в 19:00 пройдет заключительный воркшоп по теме Kotlin Coroutines 🔥

💡 Все детали встречи вы найдете ниже

Обсудим:
🔸 Recap последней темы
🔸 Пройдемся по Channels
🔸 Сделаем парочку тестов

Детали встречи:
Дата – 19 декабря, четверг в 19 вечера
Место – г. Минск прт. Победителей, д. 110, БЦ Ривьера Плаза (вход со стороны прт. Победителей), 4 этаж – офис Т-Банка
Продолжительность – 1+ час (по желанию)
Тема – Kotlin Coroutines

Жду встречи ❤️

P.S. Если не сможете найти, то пишите мне в телеграмме @PavelSha


What's new in CameraX 1.4.0 and a sneak peek of Jetpack Compose support

Вышла новая версия CameraX 1.4.0. Давайте посмотрим, что внутри:
🔸 В 1.4.0 добавили HDR-предпросмотр и Ultra HDR
🔸 HDR-предпросмотр позволяет включать HDR в Preview без необходимости привязывать VideoCapture
🔸 Чтобы полностью включить HDR, необходимо убедиться, что OpenGL способен обрабатывать определенный формат динамического диапазона:
val openGLPipelineSupportedDynamicRange = setOf(
DynamicRange.SDR,
DynamicRange.HLG_10_BIT
)
val isHlg10Supported =
cameraProvider.getCameraInfo(cameraSelector)
.querySupportedDynamicRanges(openGLPipelineSupportedDynamicRange)
.contains(DynamicRange.HLG_10_BIT)

val preview = Preview.Builder().apply {
if (isHlg10Supported) {
setDynamicRange(DynamicRange.HLG_10_BIT)
}
}

🔸 Ultra HDR добавить в пару строк кода:
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
val cameraInfo = cameraProvider.getCameraInfo(cameraSelector)
val isUltraHdrSupported =
ImageCapture.getImageCaptureCapabilities(cameraInfo)
.supportedOutputFormats
.contains(ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR)

val imageCapture = ImageCapture.Builder().apply {
if (isUltraHdrSupported) {
setOutputFormat(ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR)
}
}.build()

🔸 Добавили поддержку Composable Viewfinder, созданного на основе AndroidExternalSurface и AndroidEmbeddedExternalSurface:
class PreviewViewModel : ViewModel() {
private val _surfaceRequests = MutableStateFlow(null)

val surfaceRequests: StateFlow
get() = _surfaceRequests.asStateFlow()

private fun produceSurfaceRequests(previewUseCase: Preview) {
previewUseCase.setSurfaceProvider { newSurfaceRequest ->
_surfaceRequests.value = newSurfaceRequest
}
}

// ...
}

@Composable
fun MyCameraViewfinder(
viewModel: PreviewViewModel,
modifier: Modifier = Modifier
) {
val currentSurfaceRequest: SurfaceRequest? by
viewModel.surfaceRequests.collectAsState()

currentSurfaceRequest?.let { surfaceRequest ->
CameraXViewfinder(
surfaceRequest = surfaceRequest,
implementationMode = ImplementationMode.EXTERNAL, // Or EMBEDDED
modifier = modifier
)
}
}

🔸 В версии 1.4.0 представили две новые suspend функции для упрощения инициализации камеры и захвата изображения:
val cameraProvider = ProcessCameraProvider.awaitInstance()
val imageProxy = imageCapture.takePicture()
imageProxy.close()

🔸 Добавили mirror mode для превью через  Preview.Builder.setMirrorMode
🔸 Наложение эффектов в реальном времени. Доступен набор стандартных эффектов: Overlay Effect и Media3 Effect:
val media3Effect =
Media3Effect(
requireContext(), PREVIEW or VIDEO_CAPTURE or IMAGE_CAPTURE,
mainThreadExecutor(), {}
)
media3Effect.setEffects(listOf(RgbFilter.createGrayscaleFilter())
cameraController.setEffects(setOf(media3Effect))

🔸 Представлена ​​новая мощная фича – вспышка экрана. Вспышка экрана использует дисплей телефона:
previewView.setScreenFlashWindow(activity.getWindow());
imageCapture.screenFlash = previewView.screenFlash
imageCapture.setFlashMode(ImageCapture.FLASH_MODE_SCREEN)


Coffee&Code: Kotlin Coroutines 🤗

Пришло время последней пачки материалов по Kotlin Coroutines. Встречаемся в четверг в 19:00, чтобы закрепить и добить последние моменты по Kotlin Coroutines 🔥

💡 Все детали встречи вы найдете ниже

Обсудим:
🔸 Recap последней темы
🔸 Пройдемся по Channels
🔸 Сделаем парочку тестов

Детали встречи:
Дата – 19 декабря, четверг в 19 вечера
Место – г. Минск прт. Победителей, д. 110, БЦ Ривьера Плаза (вход со стороны прт. Победителей), 4 этаж – офис Т-Банка
Продолжительность – 1+ час (по желанию)
Тема – Kotlin Coroutines

Жду встречи ❤️

P.S. Если не сможете найти, то пишите мне в телеграмме @PavelSha


Media3 1.5.0 — what’s new?

В данной статье автор рассказывает про новинки, что появились в новом обновлении Media3 1.5.0.

🔸 Transformer теперь позволяет экспортировать неподвижное изображение или видео из фотографий с движением. Изображение фотографии с движением экспортируется, если установлена ​​длительность изображения соответствующего MediaItem. В противном случае экспортируется видео фотографии с движением
🔸 Ускорили кодирование изображения в видео благодаря оптимизации в DefaultVideoFrameProcessor.queueInputBitmap()
🔸 Transformer поддерживает AudioEncoderSettings 
🔸 В версии 1.1.0 была добавлена библиотека muxer library, чтобы создавать MP4 контейнеры. Микшер поддерживает широкий спектр аудио- и видеокодеков:
implementation ("androidx.media3:media3-muxer:1.5.0")

🔸 Чтобы использовать Transformer:
val transformer = Transformer.Builder(context)
.setMuxerFactory(InAppMuxer.Factory.Builder().build())
.build()

🔸 Добавили DefaultPreloadManager.Builder, который значительно упрощает создание компонентов предварительной загрузки и плеера:
val preloadManagerBuilder = DefaultPreloadManager.Builder()
val preloadManager = preloadManagerBuilder.build()
val player = preloadManagerBuilder.buildExoPlayer()

🔸 Добавили возможность предварительной загрузки следующего элемента в плейлист ExoPlayer:
player.preloadConfiguration =
PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L)

🔸 Предварительную загрузку плейлиста можно снова отключить с помощью PreloadConfiguration.DEFAULT:
player.preloadConfiguration = PreloadConfiguration.DEFAULT

🔸 Добавили новый модуль media3-decoder-iamf, который позволяет воспроизводить иммерсивные аудиодорожки IAMF в файлах MP4:
implementation ("androidx.media3:media3-decoder-iamf:1.5.0")

🔸 Также добавили набор extensions:
implementation ("androidx.media3:media3-common-ktx:1.5.0")


You Are Going to Need It

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

Он рассмотрел пример двух вариаций возведения числа в квадрат через использования функции pow(2) или просто переумножение числа на само себя:
@RunWith(AndroidJUnit4::class)
class MathBenchmark {
  @get:Rule
  val benchmarkRule = BenchmarkRule()

  val data = FloatArray(8_192) {
    it.toFloat() / 3f
  }

  @Test
  fun pow2() {
    benchmarkRule.measureRepeated {
      for (f in data) {
        f.pow(2f)
      }
    }
  }

  @Test
  fun square() {
    benchmarkRule.measureRepeated {
      for (f in data) {
        f * f
      }
    }
  }
}

Первые измерения показали одинаковый результат. Многие на этом шаге остановятся, хотя это будет неверно измеренный результат. Роман пошел дальше, чтобы понять почему так именно произошло. Оказалось, что все дело в оптимизациях, которые применяет ART.

Получилась простая статья, понятная и с хорошим посылом!


Kotlin Exception Handling: Why Singleton Exceptions are a bad idea

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

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


Let’s build an Android camera app! CameraX + Compose

В данной статье автор рассказывает как можно создать просто приложение камера на Compose, используя библиотеку CameraX.

🔸 CameraX имеет 4 конкретных юз кейса:
1️⃣ Предварительный просмотр
2️⃣ Захват изображения для фотосъемки
3️⃣ Видеозахват для захвата движущихся изображений и звука
4️⃣ Анализ изображения, который позволяет делать умные вещи с выходными данными камеры в реальном времени

🔸 Первым шагом добавляем зависимость библиотеки
🔸 Вторым шагом добавляем PreviewView на UI:
@Composable
fun CameraPreview(
modifier: Modifier = Modifier
) {
AndroidView(modifier = modifier,
factory = { context ->
PreviewView(context)
}
)
}

🔸 Запустив данный код получим белый экран, потому что PreviewView по сути является просто холстом для рисования
🔸 Далее нужно создать вариант использования Preview:
@Composable
fun CameraPreview( ... ) {

val previewUseCase = remember { androidx.camera.core.Preview.Builder().build() }

...
}

🔸 Затем нужно сказать Preview рисовать на холсте, предоставленном PreviewView:
AndroidView(
...
factory = { context ->
PreviewView(context).also {
previewUseCase.surfaceProvider = it.surfaceProvider
}
}
)

🔸 CameraProvider используется для привязки вариантов использования к камере и для того, чтобы делать это с учетом жизненного цикла:
@Composable
fun CameraPreview( … ) {
val localContext = LocalContext.current

LaunchedEffect(Unit) {
cameraProvider = ProcessCameraProvider.awaitInstance(localContext)
}


}

🔸 Теперь сможем связать вариант использования Preview с камерой и жизненным циклом:
@Composable
fun CameraPreview( … ) {



fun rebindCameraProvider() {
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_FRONT)
.build()

cameraProvider?.bindToLifecycle(
localLifecycleOwner,
cameraSelector,
previewUseCase
)
}


}

🔸 Функцию rebindCameraProvider() нужно вызывать только один раз, когда доступны и PreviewView, и ProcessCameraProvider:
AndroidView(modifier = modifier,
factory = { context ->
PreviewView(context).also {
previewUseCase.surfaceProvider = it.surfaceProvider
rebindCameraProvider()
}
}
)

🔸 Добавим параметр в наш Composable, чтобы можно было контролировать какую камеру использовать. Поскольку выбор камеры указан как часть привязки CameraProvider, нужно будет выполнить повторную привязку, когда она изменится:
@Composable
fun CameraPreview(
lensFacing: Int = CameraSelector.LENS_FACING_BACK,

) {


fun rebindCameraProvider() {
cameraProvider.unbindAll()
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(lensFacing)
.build()

}

LaunchedEffect(lensFacing) {
rebindCameraProvider()
}


}

🔸 В итоге результат получается такой
🔸 Еще можно добавить зум:
@Composable
fun CameraPreview(
zoomLevel: Float,
...
) {
...
LaunchedEffect(zoomLevel) {
cameraControl?.setLinearZoom(zoomLevel)
}

}

🔸 Чтобы захватить изображение с камеры в полном разрешении, нужно использовать вариант использования ImageCapture:
@Composable
fun CameraPreview(
...
imageCaptureUseCase: ImageCapture
) {
...
fun rebindCameraProvider() {
cameraProvider?.let { cameraProvider ->
val camera = cameraProvider.bindToLifecycle(
localContext as LifecycleOwner,
cameraSelector,
previewUseCase, imageCaptureUseCase
)

}
}

}


Обновление Jetpack библиотек от 11 и 12 декабря

1️⃣ Lifecycle Version 2.9.0-alpha08
🔸 Добавили ViewModelScenario.recreate для имитации System Process Death, воссоздающего тестируемую ViewModel и все связанные компоненты
🔸 Экземпляры LifecycleOwner и ViewModelStoreOwner, полученные через findViewTree, теперь можно разрешить через родительские элементы представления, такие как ViewOverlay

2️⃣ Core-Viewtree Version 1.0.0-alpha01
🔸 Первый релиз библиотеки core-viewtree
🔸 Представлена ​​концепция View, которая может иметь непересекающегося родителя. Непересекающийся родитель — это отдельный объект View, который действует как родитель представления, но не устанавливается через свойство View.parent. Примерами таких View являются ViewOverlays, всплывающие окна и диалоговые окна

3️⃣ Core-Telecom Version 1.0.0-beta01
🔸 Первая сборка новой библиотеки. Это набор API, который поможет сделать интеграцию звонков в приложение проще
🔸 Извлечение доступных audio endpoints до добавления вызова
🔸 Экспериментальная поддержка API для расширений вызовов приложений VOIP
🔸 Поддержка отображения участников группового вызова или собрания и описания того, какой участник активен и многое другое

4️⃣ ViewPager Version 1.1.0
🔸 Добавлена ​​поддержка эффекта при скролле в Android 12

Было пофикшано много багов и сделано несколько улучшений в остальных артефактах.

Также вышли артефакты для недавно представленного Android XR:
🔸 ARCore for Jetpack
🔸 Jetpack Compose for XR
🔸 Material Design for XR
🔸 Jetpack SceneCore
🔸 XR Runtime Version

Android XR – новая операционная система, созданная для следующего поколения вычислений. Созданная в сотрудничестве с Samsung, Android XR предназначена для AR и VR и очков.


Kotlin trick: writing shared Enum utility code

В данной статье автор делиться своими набросками при работе с enum в коде. К примеру, хотите, чтобы элементы enum были сразу отсортированы, то можно сделать так:
object Enums {
inline fun checkEntriesSorted() {
val names = enumEntries().map { it.name }
check(names == names.sorted()) {
"${T::class.java.simpleName} enum entries should be sorted alphabetically"
}
}
}

enum class Screen {
CartScreen,
HomeScreen,
;

companion object {
init {
Enums.checkEntriesSorted()
}
}
}

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


Vulkan 1.4: Faster app loads, less stutter and less Memory Usage

Вышел Vulkan 1.4 с важной фичей внутри – Host Image Copy на основе VK_EXT_host_image_copy. Данная фича позволяет приложению передавать данные изображения с помощью CPU вместо GPU, а также экономить память. Детали можно найти в статье.


Вопперы и табы: как мы сделали меню для Burger King

В прошлом году у Android-команды на проекте Burger King был мощный вызов: сделать редизайн главного меню. Задача была непростая по двум причинам.
Первая — легаси код. Вторая — А/В тестирование. И результат — старое меню и его логику нужно сохранить.

Решили написать меню с нуля. В данной статье автор делиться как делали часть этой фичи — табы и саб-табы.

🔸 Создать новый экран со списком блюд — это не очень сложно. Но нужно было синхронизировать скролл списка и выбор табов
🔸 Решения из коробки не было. Material-библиотека предлагает TabLayoutMediator, но он работает для связки TabLayout и ViewPager2
Что надо было решить при синхронизации табов:
📌 Синхронизация табов и списка
При скролле должны были понять, в какой категории товаров находится пользователь, и выбрать нужный таб. И наоборот — при клике на таб должен происходить подскролл списка к нужной категории

📌 Синхронизация саб-табов и списка
При выборе категории с подкатегориями появляется второй TabLayout, в котором и отображаются эти подкатегории

📌 Обратный скролл
Каждый раз нужно выбирать категорию в TabLayout при скролле в обе стороны

📌 Скролл при клике на таб
TabLayout не различает, кто кликнул по табу: пользователь или программа. Он всё равно вызывает callback OnTabSelectedListener. Это вынуждает использовать boolean-флаги, чтобы скролл пальцем и скролл по клику на таб не мешали друг другу

📌 Кастомный вид табов
У TabLayout.Tab есть поле customView. Это поле помогает легко установить верстку с иконкой и текстом

📌 Ripple-эффект табов
View в TabLayout.Tab не меняет ripple-эффект, если установлен кастомный индикатор

Далее автор рассказывает с кодом как это все у него получилось сделать 🔥


User-Agent Reduction on Android WebView

Начиная с версии Chrome 107, на Android 16 строка User-Agent по умолчанию в Android WebView будет сокращена:
Mozilla/5.0 (Linux; Android 10; K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/125.000 Mobile Safari/537.36

Это все делается, чтобы минимизировать возможность идентификации пользователя.

20 last posts shown.