Your trial period has ended!
For full access to functionality, please pay for a premium subscription
Channel age
Created
Language
Russian
-
ER (week)
-
ERR (week)

Some notes on mobile dev stuff Android | Kotlin Автор: @vla_dos

Messages Statistics
Reposts and citations
Publication networks
Satellites
Contacts
History
Top categories
Main categories of messages will appear here.
Top mentions
The most frequent mentions of people, organizations and places appear here.
Found 11 results
267
03/27/2025, 21:41
t.me/loops_everlasting/14
267
03/27/2025, 21:41
t.me/loops_everlasting/15
267
03/27/2025, 21:41
t.me/loops_everlasting/17
267
03/27/2025, 21:41
t.me/loops_everlasting/18
267
03/27/2025, 21:41
t.me/loops_everlasting/16
267
Вообще, ChatGPT прям круто прокачался

У битлов все пальцы на месте, а код на маке даже можно прочитать – как минимум, каждый символ по отдельности

В целом, тексты всё равно получаются сильно лучше, чем на последнем скрине.
Зато с самоиронией всё хорошо
03/27/2025, 21:41
t.me/loops_everlasting/19
766
Не так давно была новость, что Android 16 будет игнорировать* screenOrientation и прочие хаки, чтобы дизайнеры могли после обеда уходить домой. Но только при targetSdk = 36

Самое время вспомнить, что это за три магические константы, которые так любят на собесах

compileSdk

Так как наше приложение взаимодействует в том числе с api андроида, нужно с ним как-то скомпилироваться

Для этого у каждой версии Android SDK есть свой файлик android.jar, в котором лежит только публичный api, без имплементаций (если вы натыкались на throw new RuntimeException("Stub!”), это как раз оно). Но для того, чтобы скомпилироваться, нам этого достаточно

Таким образом, если у нас compileSdk = 35, нам недоступны api, которые появились в новой бете андроида (36) – в нашем android.jar их просто нет

Но если очень хочется, можно ли поменять цифру прямо сейчас и ничего не сломать? Да, но об этом дальше**

minSdk

Я там уже заспойлерил, что compileSdk поднимать безопасно, – давайте так и сделаем!

Теперь мы можем использовать новые api, но если запустить этот код на Vanilla Ice Cream, будет крэш в рантайме с LinkageError. Поэтому тулинг злобно подчеркивает такие места, заставляя нас делать дополнительные проверки

Чтобы не добавлять их везде, где андроид зачем-то эволюционировал, как раз нужен minSdk. Какая разница, что метод доступен только в 16 api, когда у нас поддержка начинается с 21?

targetSdk

Тут самое интересное

Андроид регулярно эволюционирует, а иногда даже ломает парадигмы. Так, в версии 23 выяснилось, что получать все пермишены при установке это не дело – нужно дать пользователю право отказаться от прослушки или от доступа к списку друзей контактов

Но в сторах уже есть миллионы приложений, которые об этом ничего не знают, и шквал единиц в Google Play это вопрос времени

Помните, я выше говорил, что повышать compileSdk безопасно? Так вот, всё дело в targetSdk

Все эти приложения не сломаются только потому, что им можно играть по старым правилам. Если targetSdk = 22, то пермишены выдаются автоматически (и были даже кое-какие известные приложения, которые этим пользовались)

Как это работает?

По сути это такие же проверки, которые мы добавляем, чтобы ничего не сломать на более старых версиях, но на этот раз уже со стороны самого андроида:


/**
* Return true if we are in RTL compatibility mode (either before Jelly Bean MR1 or
* RTL not supported)
*/
private boolean isRtlCompatibilityMode() {
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
return targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1 || !hasRtlSupport();
}


Или вот

На первый взгляд низкий таргет выглядит как хак, чтобы обходить ограничения гугла. Но нет – в какой-то момент лавочку прикрыли, и теперь гугл каждый год инкрементит минимальный targetSdk, с которым можно опубликовать приложение

В этом году будет 35

@loops_everlasting

*худ. преувел.; исключения и прочие нюансы тут

**был любопытный кейс, когда всё сломалось, но по другой причине
03/05/2025, 11:48
t.me/loops_everlasting/13
418
public final override

Помимо разработки библиотек, есть еще один кейс, где эсплицитный public несет дополнительную смысловую нагрузку


abstract class Animal {

protected abstract fun say(): String
}

abstract class Fox : Animal() {

public override fun say(): String {
return "Ring-ding-ding-ding-dingeringeding!"
}
}

class ArcticFox : Fox() {

// тут public уже не нужен
override fun say(): String {
return "Wa-pa-pa-pa-pa-pa-pow!"
}
}

class FennecFox : Fox() {

// тут тоже
override fun say(): String {
return "Chacha-chacha-chacha-chow!"
}
}


С точки зрения синтаксиса котлин такое позволяет, и тулинг не подсвечивает его как redundant

А еще в котлине есть ключевое слово final

Концептуально это очень похоже на предыдущий кейс, ибо и public, и final здесь выполняют одну и ту же функцию – нивелируют предыдущую эксплицитную декларацию: public можно использовать для того, чтобы перекрыть protected / internal, а final – для open / abstract


open class RedFox : Fox() {

final override fun say(): String {
return "A-hee-ahee ha-hee!"
}
}

class EuropeanRedFox : RedFox() {

// ошибка – “’say' in 'RedFox' is final and cannot be overridden”
override fun say(): String {
return "A-oo-oo-oo-ooo! Woo-oo-oo-ooo!"
}
}



@loops_everlasting
02/28/2025, 18:41
t.me/loops_everlasting/12
438
public class String

Вы могли видеть такую функцию:


public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}


Или такую:


public inline fun mutableListOf(): MutableList = ArrayList()


Что у них общего? Они обе на котлине, и обе объявлены с модификатором public

🤔 Зачем это нужно в лаконичном языке, где сущности и так public по умолчанию?

В Kotlin 1.4 появился опциональный флаг, который обязывает везде указывать модификатор доступа, а для публичного api еще и возвращаемый тип

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

Кстати, про то, как никому ничего не сломать, на днях был доклад у @dolgo_polo_dev. И там на секции вопросов подняли тему, что делать, если у нас несколько модулей

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

У нас нет никакого модификатора, который был бы internal внутри library group, но есть специальная аннотация @RequiresOptIn. Она дает возможность сообщить пользователю, что api is not meant to be public. Например, так это выглядит в корутинах:


@Retention(value = AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS, AnnotationTarget.PROPERTY)
@RequiresOptIn(
level = RequiresOptIn.Level.ERROR, message = "This is an internal kotlinx.coroutines API that " +
"should not be used from outside of kotlinx.coroutines. No compatibility guarantees are provided. " +
"It is recommended to report your use-case of internal API to kotlinx.coroutines issue tracker, " +
"so stable API could be provided instead"
)
public annotation class InternalCoroutinesApi



@InternalCoroutinesApi
public fun tryResume(value: T, idempotent: Any? = null): Any?


Теперь, чтобы вызвать tryResume, нам нужно явно добавить либо @OptIn(InternalCoroutinesApi::class) (если мы хотим "проглотить" ошибку), либо @InternalCoroutinesApi (если хотим пробросить ее дальше). В обоих случаях мы по сути подписываемся, что согласны на нестабильное api – то есть, технически все эти сущности публичные, но есть нюанс

При этом кейворд public может быть полезен, даже когда вам не нужно предоставлять внешнее api

Но об этом уже в следующем посте

🎃 Everlasting Loops
02/23/2025, 21:29
t.me/loops_everlasting/11
529
кто бы мог подумать, что картинку спойлерить можно, а monospace и code block – нельзя
02/05/2025, 13:55
t.me/loops_everlasting/10
495
Разгадка

Дело в том, что гугл не отстает и тоже переводит свои библиотеки на KMP – таким образом, у артефакта есть какой-то общий id (например, lifecycle-viewmodel), а для конкретного таргета уже выбирается зависимость с соответствующим суффиксом. В итоге некоторые транзитивные зависимости зарезолвились как-то так:


+--- androidx.compose.material3:material3-desktop:{strictly 1.3.0} -> 1.3.0 (c)
+--- androidx.lifecycle:lifecycle-viewmodel-compose-desktop:{strictly 2.8.0} -> 2.8.0 (c)
+--- androidx.compose.ui:ui-jvmstubs:{strictly 1.7.2} -> 1.7.2 (c)


Только суффикс должен был быть -android.

Напишите, кстати, в комменты, кто сталкивался с таким. Кажется, что это не очень тривиально – практически все пишут на котлине, и когда мы подключаем Kotlin Gradle plugin в модуль, зависимости резолвятся с ожидаемым суффиксом:


+--- androidx.compose.material3:material3-android:{strictly 1.3.0} -> 1.3.0 (c)
+--- androidx.lifecycle:lifecycle-viewmodel-compose-android:{strictly 2.8.3} -> 2.8.3 (c)
+--- androidx.compose.ui:ui-android:{strictly 1.7.2} -> 1.7.2 (c)


Бридж, который связывает слой нативного андроида и Unity, был написан на Java; Kotlin-плагина в проекте не было вообще.

Ну а дальше всё просто: каких-то сущностей не оказалось в -desktop/-jvmstubs–артефактах –> в рантайме был крэш с LinkageError.

P.S. Начиная с AGP 8.4 такой проблемы больше нет. На проекте был 8.3
02/05/2025, 12:51
t.me/loops_everlasting/9
Search results are limited to 100 messages.
Some features are available to premium users only.
You need to buy subscription to use them.
Filter
Message type
Similar message chronology:
Newest first
Similar messages not found
Messages
Find similar avatars
Channels 0
High
Title
Subscribers
No results match your search criteria