The creation of this channel was inspired by @themishkun. You can subscribe to him
Telegram
Голос из-под шторки | Миша Левченко
Продуктивность разработки, обзоры книжечек, мемы и жиза от @themishkun
Наконец добрался до канала, зато с новостями
На досуге сделал плагин для Intellij, решил написать об этом статью, и ее опубликовали в ProAndroidDev! Claps or any feedback appreciated, а сам плагин можно посмотреть тут
В ближайшее время постараюсь сделать перевод для хабра
На досуге сделал плагин для Intellij, решил написать об этом статью, и ее опубликовали в ProAndroidDev! Claps or any feedback appreciated, а сам плагин можно посмотреть тут
В ближайшее время постараюсь сделать перевод для хабра
Medium
Better Kotlin–Java Auto-Complete: How to Improve Code Completion Suggestions in Mixed Projects
If you write in Kotlin, you may know that for data classes the compiler generates lots of stuff for us, including componentN() functions…
а вот, кстати, и перевод
пока переводил, обнаружил, что в оригинале удалили гифку при публикации. видимо, решили, что в серьезных статьях не место каким-то там гифкам, но получилось так, что драматическая пауза не была соблюдена, мы сразу перешли к спойлеру
на хабре гифка локализована; оригинал – в первом комментарии
пока переводил, обнаружил, что в оригинале удалили гифку при публикации. видимо, решили, что в серьезных статьях не место каким-то там гифкам, но получилось так, что драматическая пауза не была соблюдена, мы сразу перешли к спойлеру
на хабре гифка локализована; оригинал – в первом комментарии
Хабр
Улучшаем автокомплит в смешанных Java-Kotlin проектах
Честно говоря, не знаю, нужно ли ставить тэг «перевод» на собственную статью. Ну ок, поставил. Убрал для Технотекста – надеюсь, не зря! Всем привет! Недавно я наткнулся на статью , где...
Kotlin function types
ответил в комментариях в Android Good Reads, но решил и тут написать, вдруг кому-то будет интересно
допустим, у нас есть такой Java-класс
и такой Kotlin-класс
чтобы передать лямбду типа
и если запустить, можно увидеть, что после выполнения
напишите в комменты, кому это было очевидно!
ответил в комментариях в Android Good Reads, но решил и тут написать, вдруг кому-то будет интересно
допустим, у нас есть такой Java-класс
public class SomeJavaClass {
interface Listener {
void onEvent(String event);
}
private HashSet<Listener> listeners = new HashSet<>();
void addListener(Listener listener) {
listeners.add(listener);
}
void removeListener(Listener listener) {
listeners.remove(listener);
}
}
и такой Kotlin-класс
class SomeKotlinClass {
private val eventListenerLambda = { eventId: String ->
println(eventId)
}
fun testLambdas(someJavaClass: SomeJavaClass) {
someJavaClass.addListener(eventListenerLambda)
someJavaClass.removeListener(eventListenerLambda)
}
}
чтобы передать лямбду типа
(String) -> Unit
в качестве параметра типа Listener
, компилятор либо использует method reference, либо, когда Java 6, заворачивает в адаптер, и в обоих случаях инстанс итогового Listener
каждый раз получается разный
public final class SomeKotlinClass {
@NotNull
private final Function1 eventListenerLambda = SomeKotlinClass::eventListenerLambda$lambda$0;
public static final int $stable;
public final void testLambdas(@NotNull SomeJavaClass someJavaClass) {
Intrinsics.checkNotNullParameter(someJavaClass, "someJavaClass");
Function1 var2 = this.eventListenerLambda;
someJavaClass.addListener(SomeKotlinClass::testLambdas$lambda$1);
var2 = this.eventListenerLambda;
someJavaClass.removeListener(SomeKotlinClass::testLambdas$lambda$2);
}
private static final Unit eventListenerLambda$lambda$0(String eventId) {
Intrinsics.checkNotNullParameter(eventId, "eventId");
System.out.println(eventId);
return Unit.INSTANCE;
}
private static final void testLambdas$lambda$1(Function1 $tmp0, String event) {
$tmp0.invoke(event);
}
private static final void testLambdas$lambda$2(Function1 $tmp0, String event) {
$tmp0.invoke(event);
}
}
и если запустить, можно увидеть, что после выполнения
someJavaClass.removeListener(eventListenerLambda)
содержимое listeners
не изменитсянапишите в комменты, кому это было очевидно!
LinkageError
На днях готовил к релизу наш SDK для Unity. И всё должно было быть просто, потому что это обертка над нативными, самое сложное позади
А на нативном SDK для Android у нас есть UI-фича, которая сделана на Compose
И вот при взаимодействии с ней в Unity-проекте всё крэшилось с
На нативном SDK при этом всё хорошо; другие пакеты в Unity не привносят конфликтующие androidx-зависимости
Если кто-то догадался, что произошло, пишите в комменты
На днях готовил к релизу наш SDK для Unity. И всё должно было быть просто, потому что это обертка над нативными, самое сложное позади
А на нативном SDK для Android у нас есть UI-фича, которая сделана на Compose
И вот при взаимодействии с ней в Unity-проекте всё крэшилось с
java.lang.NoClassDefFoundError
, в рантайме почему-то не нашлось класса, который был при компиляции. Это частный случай java.lang.LinkageError
– обычно такие ситуации возникают, когда dependencyA уже ранее была скомпилирована с dependencyB, но в рантайме оказалась другая версия dependencyB, с несовместимым API, либо её вовсе нет. А так как всё уже было скомпилировано ранее, ошибка происходит в рантаймеНа нативном SDK при этом всё хорошо; другие пакеты в Unity не привносят конфликтующие androidx-зависимости
Если кто-то догадался, что произошло, пишите в комменты
Everlasting Loops – Mobile | Android | Kotlin
LinkageError На днях готовил к релизу наш SDK для Unity. И всё должно было быть просто, потому что это обертка над нативными, самое сложное позади А на нативном SDK для Android у нас есть UI-фича, которая сделана на Compose И вот при взаимодействии с ней…
Разгадка
Дело в том, что гугл не отстает и тоже переводит свои библиотеки на KMP – таким образом, у артефакта есть какой-то общий id (например,
Только суффикс должен был быть
Напишите, кстати, в комменты, кто сталкивался с таким. Кажется, что это не очень тривиально – практически все пишут на котлине, и когда мы подключаем Kotlin Gradle plugin в модуль, зависимости резолвятся с ожидаемым суффиксом:
Бридж, который связывает слой нативного андроида и Unity, был написан на Java; Kotlin-плагина в проекте не было вообще.
Ну а дальше всё просто: каких-то сущностей не оказалось в
P.S. Начиная с AGP 8.4 такой проблемы больше нет. На проекте был 8.3
Дело в том, что гугл не отстает и тоже переводит свои библиотеки на 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
кто бы мог подумать, что картинку спойлерить можно, а monospace и code block – нельзя
public class String
Вы могли видеть такую функцию:
Или такую:
Что у них общего? Они обе на котлине, и обе объявлены с модификатором
🤔 Зачем это нужно в лаконичном языке, где сущности и так
В Kotlin 1.4 появился опциональный флаг, который обязывает везде указывать модификатор доступа, а для публичного api еще и возвращаемый тип
Эта фича нужна разработчикам библиотек, чтобы лучше контролировать этот самый публичный api. Если вы нигде не можете пропустить модификатор(код попросту не скомпилируется) , скорее всего вы укажете тот доступ, который и планировали. В результате будет гораздо меньше мест, где изменения могут что-то сломать у текущих клиентов
Кстати, про то, как никому ничего не сломать, на днях был доклад у @dolgo_polo_dev. И там на секции вопросов подняли тему, что делать, если у нас несколько модулей
Тут нет идеального варианта даже внутри котлина, но есть компромиссный
У нас нет никакого модификатора, который был бы internal внутри library group, но есть специальная аннотация
Теперь, чтобы вызвать(если мы хотим "проглотить" ошибку) , либо (если хотим пробросить ее дальше) . В обоих случаях мы по сути подписываемся, что согласны на нестабильное api – то есть, технически все эти сущности публичные, но есть нюанс
При этом кейворд
Но об этом уже в следующем посте
🎃 Everlasting Loops
Вы могли видеть такую функцию:
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 <T> mutableListOf(): MutableList<T> = 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
При этом кейворд
public
может быть полезен, даже когда вам не нужно предоставлять внешнее apiНо об этом уже в следующем посте
Please open Telegram to view this post
VIEW IN TELEGRAM
public final override
Помимо разработки библиотек, есть еще один кейс, где эсплицитный
С точки зрения синтаксиса котлин такое позволяет, и тулинг не подсвечивает его как redundant
А еще в котлине есть ключевое слово
Концептуально это очень похоже на предыдущий кейс, ибо и
@loops_everlasting
Помимо разработки библиотек, есть еще один кейс, где эсплицитный
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
Telegram
Everlasting Loops
public class String
Вы могли видеть такую функцию:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val…
Вы могли видеть такую функцию:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val…
Не так давно была новость, что Android 16 будет игнорировать* и прочие хаки, чтобы дизайнеры могли после обеда уходить домой. Но только при
Самое время вспомнить, что это за три магические константы, которые так любят на собесах
compileSdk
Так как наше приложение взаимодействует в том числе с api андроида, нужно с ним как-то скомпилироваться
Для этого у каждой версии Android SDK есть свой файлик(если вы натыкались на throw new RuntimeException("Stub!”), это как раз оно) . Но для того, чтобы скомпилироваться, нам этого достаточно
Таким образом, если у нас
Но если очень хочется, можно ли поменять цифру прямо сейчас и ничего не сломать? Да, но об этом дальше**
minSdk
Я там уже заспойлерил, что
Теперь мы можем использовать новые api, но если запустить этот код на Vanilla Ice Cream, будет крэш в рантайме с LinkageError. Поэтому тулинг злобно подчеркивает такие места, заставляя нас делать дополнительные проверки
Чтобы не добавлять их везде, где андроид зачем-то эволюционировал, как раз нужен
targetSdk
Тут самое интересное
Андроид регулярно эволюционирует, а иногда даже ломает парадигмы. Так, в версии 23 выяснилось, что получать все пермишены при установке это не дело – нужно дать пользователю право отказаться от прослушки или от доступа к спискудрузей контактов
Но в сторах уже есть миллионы приложений, которые об этом ничего не знают, и шквал единиц в Google Play это вопрос времени
Помните, я выше говорил, что повышать
Все эти приложения не сломаются только потому, что им можно играть по старым правилам. Если(и были даже кое-какие известные приложения, которые этим пользовались)
Как это работает?
По сути это такие же проверки, которые мы добавляем, чтобы ничего не сломать на более старых версиях, но на этот раз уже со стороны самого андроида:
Или вот
На первый взгляд низкий таргет выглядит как хак, чтобы обходить ограничения гугла. Но нет – в какой-то момент лавочку прикрыли, и теперь гугл каждый год инкрементит минимальный
В этом году будет 35
@loops_everlasting
*худ. преувел.; исключения и прочие нюансы тут
**был любопытный кейс , когда всё сломалось, но по другой причине
screenOrientation
targetSdk = 36
Самое время вспомнить, что это за три магические константы, которые так любят на собесах
compileSdk
Так как наше приложение взаимодействует в том числе с api андроида, нужно с ним как-то скомпилироваться
Для этого у каждой версии Android SDK есть свой файлик
android.jar
, в котором лежит только публичный api, без имплементаций Таким образом, если у нас
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
*худ. преувел.; исключения и прочие нюансы тут
**
Вообще, ChatGPT прям круто прокачался
У битлов все пальцы на месте, а код на маке даже можно прочитать – как минимум, каждый символ по отдельности
В целом, тексты всё равно получаются сильно лучше, чем на последнем скрине.
Зато с самоиронией всё хорошо
У битлов все пальцы на месте, а код на маке даже можно прочитать – как минимум, каждый символ по отдельности
В целом, тексты всё равно получаются сильно лучше, чем на последнем скрине.
Зато с самоиронией всё хорошо