Warning: Undefined array key 0 in /var/www/tgoop/function.php on line 65

Warning: Trying to access array offset on value of type null in /var/www/tgoop/function.php on line 65
242 - Telegram Web
Telegram Web
Теперь совсем немного поговорим о том, как раздавать права в тестах. Смотрите, когда мы говорим про разрешения в тестах, очень вероятно речь идет именно о UI тестах, в других тестах мы обычно не завязываемся на Android. В UI тестах у нас есть два основных компонента это Driver и Runner.

🛞Driver – то, с помощью чего мы вообще можем тыкать по кнопкам из теста и проверить, что там отрисовалось на экране. В Android их два Espresso и UIAuthomator, все остальные это либо производные этих двух либо всеми забытая технология.

🏃‍♂️Runner – штука которая запускает тесты на эмуляторе или нескольких. Помимо этого Runner берет на себя работу по установке нужного окружения, перезапуска тестов, сбора статистики, сбор результатов с устройства и т.д. Самые распространенные Runner для тестов это Fork и Maraphon. Есть еще куча других, просто эти самые популярные. Смотрите, тут может возникнуть вопрос, а для чего нужны сторонние Runner ведь у Android фреймворка есть свой. Cтандартный Runner фреймворка не умеет параллелить тесты м/у несколькими эмуляторами, и не умеет перезапускать флакающие тесты. Есть хотите узнать чуть больше про Runner и Driver, про них можно почитать в этой статье.

Раздача прав для тестов, может быть реализована как на уровне 🛞 Driver так и на стороне 🏃‍♂️Runner. Оба тупо используют команды adb. Runner при установке приложения, а Driver в момент выполнения теста. Сама возможность вызывать команды adb нам доступна потому, что у нас тесты всегда запускаются на эмуляторе или устройстве в котором включена настройка разработчика и отладка по usb. Благодаря этому мы можем использовать adb как нам захочется.

Почти во всех Runner есть настройка, позволяющая выдать все возможные runtime права сразу при установке. И вся эта магия работает путем просто прибавления к команде adb install флага -g.

Если говорим про выдачу прав во время выполнения теста, т.е через Driver. Иногда такое нужно если вы хотите протестить какой-то функционал связанный с разрешениями. Можно использовать специальную TestRule которая идет вместе с тестовым фреймворком GrantPermissionRule. Если заглянуть внутрь, то все что она делает, это берет UIAutomator у которого есть доступ к adb, и выполняет команду ‘grand permission’. Помимо этого, можно самим взять UIAutomator и раздать или отобрать права у приложения.

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

А теперь навалю кликбейта. Если хотите серию постов про продробное описание того, как устроена инфраструктура тестирования в больших компаниях, наберите 100 лайков под постом)
👍85🔥10👏6
Гребаные дженерики

👇
🥰45🤯6
Знаете такое выражение, научишься кататься на велосипеде и всю жизнь умеешь. Однако есть три вещи, с которыми все работает наоборот. Каждый раз как первый. Это регулярные выражения, bash и дженерики. Все знают как это использовать на практике, но когда речь заходит о том, чтобы рассказать про них, начинаются проблемы. 

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

Первое с чего начнем это с полиморфизма. Один из трех китов ООП про который любят спрашивать джунов. Дженерики это один из подвидов полиморфизма. Да, полиморфизма есть несколько типов: на базе наследованная, на базе дженериков и на базе перезагрузки (overloading на русском звучит странно). 

Чтобы понять суть дженериков, достаточно понять как они были придуманы. А придуманы они были вот так – сидел программист и думал: “вот у меня есть функция, я хочу чтобы она работала с любыми типами, впадлу копипастить для каждого типа вручную”. Конечно если мы говорим про Java, то можно просто поставить тип параметров Object, однако не во всех языка есть Object или Any. 

Чтобы решить проблему, и не писать на каждый новый тип новую функцию, придумали дженерики. Пишем функцию, указываем что параметры могут быть любого типа или принадлежать множеству каких-то типов и вуаля, компилятор все сделает за нас. Это база, нужная для дальнейшего понимания. 

В чем нюанс дженериков на Java? В том, что они появились только в 5-й версии. При этом создатели Java сильно придерживаются обратной совместимости. Другими словами вы можете написать код с использованием Java 1, и он скомпилируется при использовании Java версии 17. Поэтому нужно было сделать дженерики таким образом, чтобы код ДО дженериков тоже мог компилироваться. 

Из-за этого разработчикам пришлось сделать костыль. Когда вы используете дженерики, компилятор стирает к чертям все ваши дженерики и ставит туда Object, а в местах использования сам добавляем приведение к нужному типу. Для сравнения в C++, дженерики (там они называются template) работают путем генерации кучи других классов и методов на каждый используемый тип. Вы пишите List<Integer>, компилятор C++ это видит и генерирует код List именно под Integer. 

Самый частый вопрос на собесах, можно ли узнать тип дженерика в runtime. Краткий ответ – нет, нельзя. Более развернутый: можно, но нужно заняться сексом с рефлексией в плохом смысле этого слова. Короче, если вы пишете List<Integer>, то в результате будет просто List без указания дженерика или List<Object>. 

Kotlin появился гораздо позже Java, и  в нем уже нельзя создать коллекции без указания дженерика. Однако из-за того, что код на Kotlin должен быть совместим в Java, в нем дженерики также стираются.
👍758🥰2
Инвариантность, ковариантность и контравариантность

👇
👍45🔥2
Идем дальше, есть три понятия инвариантность, ковариантность и контравариантность. Не пугайтесь названий, сейчас все раскидаем, что поймет каждый. Буду объяснять на примере List и двух классов Developer и MobileDeveloper . Как вы понимаете MobileDeveloper наследует Developer, т.е Developer стоит выше по иерархии наследования. Это значит что мы любую ссылку на MobileDeveloper можем привести к Developer. Типо такого:

MobileDeveloper mobileDeveloper = MobileDeveloper()
Developer developer = mobileDeveloper 


Прежде чем пойдем дальше стоит понять такую штуку, как производный тип. Производный тип получается когда один класс, является одним из компонентов другого типа. Проще на примере, для наших классов это может быть – List<Developer>. List<Developer> – отдельный тип, однако в него входит наш Developer. Думаю тут суть ясна.

Значит, инвариантность это когда List<Developer> и List<MobileDeveloper> это два абсолютно разных типа. Другими словами мы не можем один тип привести к другому. Уже нельзя взять и привести ссылку на список List<MobileDeveloper>, к List<Developer> вас не пропустит компилятор. Все дженерики в Java и в Kotlin по дефолту инвариантные.

Ковариантность это сохранение иерархии в производных типах. Или проще, ковариантность это когда List<MobileDeveloper> является наследником List<Developer>. Раз он наследник, значит можно приводить одну ссылку в другой. Примерно так: 

List<MobileDeveloper> mobileDevelopers = new ArrayList<>();
List<? extends Developer> developers = mobileDevelopers;


Как вы заметили для этого нужно было использовать ? extends Developer. Это и есть синтаксическая реализация ковариантности в Java. Работает это примерно так, когда мы используем строку ? extends Developer мы говорим комплятору, в текущем списке, лежат объекты, которые 100% можно привести к Developer, ведь они или и есть Developer или ниже по иерархии наследованния. 

Для чего это нужно? Очень удобно теперь делать функцию, которая итерируется по списку List<? extends Developer>. Ведь теперь не имеет значение какой именно наследник внутри этого списка. Помимо этого, мы теперь не можем в списке использовать модифицирующие операторы. А вот почему не можем, расскажу в следующем посте.

И последний компонент это Контравариантность. Он противоположен ковариантности. Другими словами контравариантность это когда List<Developer> является наследником List<MobileDeveloper>. Иерархия в производных типах поворачивается на 180 градусов. А пример вот такой:

List<Developer> developers = new ArrayList<>();
List<? super MobileDeveloper> mobileDevelopers = developers;


Синтаксически контравариантность реализуется при помощи ? super MobileDeveloper. Этим мы говорим компилятору, что в списке mobileDevelopers либо MobileDeveloper, либо выше по иерархии. 

Нужно это для того, чтобы делать функции заполнения. Аля такой, вот у меня метод fill(), он принимает вот такой список List<? super MobileDeveloper>. Теперь я могу передавать в этот метод как List<Developer> так и List<MobileDeveloper>, и он отработает одинаково хорошо. Ровно как и с Ковариантностью, у нас минус одна операция. Когда используем ? super MobileDeveloper нельзя использовать операции чтения. Если попробовать использовать get, компилятор упадет. 

Откуда такие ограничения на чтение и запись при контравариантности и ковариантности расскажу в следующем посте. Это одна из самых сложных вещей в дженериках, поймете это, и больше у вас никогда не возникнет проблем.
👍74🔥7👏21
Значит, смотрите ковариантность или ? extends Developer у нас накладывает ограничение на запись, и вообще всех методов, где дженерик это один из аргументов. Контравариантность накладывает ограничения на чтение, или всех методов, где джереник это тип возвращаемого элемента. Почему так?

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

Начнем с ковариантности. Причина этому такая, ведь когда ставим ? extends Developer мы говорим компилятору, что нам нужна гарантия, что объекты внутри будут либо Developer, либо ниже по иерархии. Однако, во время вставки компилятор не может понять какой именно тип дженерика нужно использовать в методе set. Мы не можем указать тип ссылки вставляемого объекта как ? extends Developer, это синтаксически невозможно. И раз так, мы не можем использовать любые методы, где аргумент это дженерик. Вот поясняющий пример:

List<? extends Number> list = new ArrayList<Double>();
list.add(new Integer(1));

Ссылка на список у нас ? extends Number и как бы кажется, что мы можем безопасно вставлять Number и все что ниже. Однако объект, на который ссылается list это список именно Double. И если бы компилятор тут позволил сделать вставку, то после при чтении из такого листа мы бы просто упали с ClassCastException

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

List<? super Double> list = new ArrayList<Number>();
list.get(0);

У нас ссылка ? super Double, что означает что в объекте списка, на который ссылается эта ссылка, либо объекты Double, либо выше по иерархии, вообще хз. И как в таком случае компилятору кастить объект на выходе? Правильно, никак, поэтому если мы читаем из такого листа, то можем получить только Object, т.к все объекты ниже по иерархии.

Я понимаю, что это может звучать супер запутанно, чтобы понять, еще раз перечитайте прошлый пост и повторите примеры. Потрогать эту штуку руками это самый рабочий метод понять эту теорию. Помимо этого, очень сильно рекомендую прочитать про правило PECS. После прочтения этого правила у вас все встанет на свои места.
👍448👎1
В предыдущем посте был коммент что у начинающих может возникнуть вопрос, вроде: ну вот ковариантность и контравариантность, а кроме собесов то, где это использовать? Вопрос хороший, постараюсь ответить. 

Первое, что тут посоветую пойти, найти книгу “Эффективная Java”, затем прочитать главу про правило PECS (Producer extend, Consumer super). В этой главе полностью и на достаточно глубоком уровне дается ответ на этот вопрос. Вообще на мой взгляд это лучшая книга по Java которую я читал, многие вещи правда уже не актуальны, однако большая часть советов просто топ. Поэтому крайне рекомендую прочитать полностью.

Если же хочется более краткого и простого ответа, то… его не будет. Серьезно, я тут даю вам зонтик от душнил, а не пытаюсь объяснить досконально каждую вещь в Java, на это и жизни не хватит) Для этого есть книги, вроде той, что описал выше и видосы наших друзей из Индии.
👍24😁8
Отличия в дженериках Kotlin и Java

👇
👍19
На самом деле по большей части отличий довольно мало. Первое отличие синтаксическое ? super заменили на in, ? extends заменили на out:

List<? extends Developer> developers = new ArrayList<MobileDeveloper>();
val developers: List<out Developer> = ArrayList<MobileDeveloper>();

List<? super Double> numbers = new ArrayList<Number>();
val numbers: List<in Double> = ArrayList<Number>();


Помимо этого теперь если у класса есть дженерик, мы не сможем создать объект этого класса без указания какого либо дженерика, как это было в Java. Это все довольно простые вещи, однако у дженериков в Kotlin есть два отличия, которые сильно все меняют. 

Первое, теперь можно в объявлении класса указать ковариантность и контравариантность. Смотрите, в Java мы эти вещи могли указывать только на уровне ссылок. В Kotlin эту вещь упростили, и например если мы хотим ковариантность сразу на всех объектах нашего класса, достаточно в объявлении указать out возле дженерика. На практике это работает так:

val numbers: List<Int> = listOf<Number>();
 
Видите в чем прикол. В Kotlin у List дженерик помечен как out на уровне интерфейса. Это автоматически дает свойство ковариантности всем объектам этого List. Аналогично работает и свойство контравариантности, для этого нужно поставить in

В предыдущем посте я писал про то, что свойство ковариантность и контравариантность накладывает ограничения на запись или чтение. В Kotlin эти ограничения сохраняются. И если вы в дженерике класса поставите out, вы не сможете создать функцию в этом классе, такую, где дженерик был бы входным аргументом. То же самое и про in, там ограничения на функции, где дженерик это возвращаемый тип.

Второе отличие, это возможность получить тип дженерика в runtime без регистрации, смс и рефлексии. В Kotlin ввели inline функции, т.е. функции, код которых встраивается в место использования. И вот, раз этот код будет заинлайнен, то по идее мы можем сразу узнать тип конкретного дженерика. Правда для этого нужно использовать ключевое слово reified

inline fun <reified T : Any> fn() {
    val kClass = T::class
    println("Тип дженерика $kClass")
}


Подводя итог, основных отличия три:
Нельзя создавать класс без указания дженерика
Свойство ковариантность и контравариантность можно сразу задать всем объектам класса 
Через reified можем получить тип дженерика в runtime
🔥405
Два друга @JvmWildcard и @JvmSuppressWildcards.

В комментах накинули вопрос про @JvmSuppressWildcards. Тоже довольно неочевидная штука, но порой приходится разбираться. Суть в чем, Java и Kotlin полностью совместимы, что означает что из Java можно вызывать Kotlin, и наоборот. На стыке этих двух языков начинаются приколы, например в Kotlin все типы делятся на Nullable и NotNullable, а в Java такого разделения нет. 

Работа дженериков тоже немного отличается. Буквально в предыдущем посте я описывал, что ковариантность и контравариантность в Kotlin может работать на уровне классов, а не ссылок. И вот пример:

class Box<out T>(val value: T)

fun box(value: Integer): Box< Integer> = Box(value)
fun unbox(box: Box<Number>): Number = box.value


Как видите в Box дженерик помечен как out, что означает ковариантность на уровне всех объектов. Только это работает на уровне Kotlin. Если попытаемся вызвать метод box из Java, то он вернет Box без дженерика. Это как бы немного нарушает логику ковариантности которую обещает Kotlin. При этом в методе unbox автоматически появится wildcard. В итоге в Java получим вот это:

Box box(Integer value)
Number unbox(Box<? extend Number>)


В целом все не так страшно, однако иногда охото подкрутить это поведение. Например, мы не хотим, чтобы в методе unbox автоматически проставлялся wildcard. Или же напротив проставлялся в результатах функции, в методе box. И вот для этого, нужны наши волшебные аннотации. Как всегда, лучше на примере.

Делаем в Kotlin:  

fun box(value: Integer): Box<@JvmWildcard Integer> = Box(value)

Получаем в Java:

Box<? extend Integer> box(Integer value)

Делаем в Kotlin:  

fun unbox(box: Box<@JvmSuppressWildcards Number>): Number = box.value

Получаем в Java:

Number unbox(Box<Number> box)

Сравните эти две полученные функции с дефолтным поведением и суть станет ясна.

Где это нужно на практике? У меня такое было только при работе с Dagger. Фишка в чем, в том что Dagger работает на базе Kapt. Если вдруг не знали, то kapt работает в два приседания. Сперва он перегоняет весь код в Java Stub и только потом генерит нужный код. Поэтому он такой долгий и по этой причине иногда нужно делать подсказки компилятору, когда пишем модули на Kotlin. В противном случае кодогенератор начинает чудить и падать.
🔥23👍4🤔1
Соглашение по конфигурации

👇
😁91👍1
Все же хотя бы краем уха слышали про Ruby On Rails. Пожалуй самый знаменитый из всех веб фреймворков, которые есть в индустрии. Он получился настолько удачным, что в принципе язык Ruby редко рассматривается без Ruby On Rails. Фреймворк крут тем, что принес много интересных концепций. 

Одна из таких концепций – соглашения по конфигурации (Convention over configuration). Суть в том, что когда мы затаскиваем какую-то либу, или настраиваем фреймворк, или билд систему нам нужно сделать конфигурацию, хотя бы минимальную. И когда вы делаете это в первый раз, то особых проблем это не вызывает. Но на 5-10 раз начинаешь бомбить из-за копипасты. И вот концепция соглашения вместо конфигурации позволяет решить эту проблему и уменьшить количество дублируемого кода.

Работает это так: мы просто делаем систему такой, чтобы все работало сразу из коробки без минимальной конфигурации. И только в том случае, если тебе нужно подкрутить дефолтное поведение, ты уже делаешь конфигурацию. Ruby On Rails для примера запускает целый сервак без какой-либо настройки. Из-за этого кстати его все боялись, когда фреймворк только вышел. Потому как всем казалось, что не может быть так просто и ты явно за это где-то заплатишь. 

На практике в Android разработке этот подход можно использовать в Gradle. Когда у вас в проекте 2-3 модуля, можно просто копировать конфигурацию. Однако когда их переваливает за 500, копипастой уже не отделаешься. Простое изменение параметра может привести к тому, что придется переделывать везде, да и новые модули создавать запарно. Эту проблему и помогает решить наша концепция. 

Соглашения в Gradle можно реализовать в виде своих плагинов, у Gradle даже есть дока для этого. Просто выносим конфигурацию в такой плагин, и все что остается сделать это в новом подключаемом модуле просто заиспользовать этот самый плагин. Все работает как нужно, прям из коробки, а если нужно в отдельном модуле подкрутить поведение, делаешь это только в этом конкретном модуле.
👍312🤔1
Подходы работы с Git

👇
👍9😁4
{1/2} Немного отойдем от разговора о языке и поговорим о инженерных практиках, а конкретно про работу с гитом. Так уж получилось что если говорить о гите, то у нас два стула, основных подхода как можно с ним работать (нет шутка про стулья меня еще не заебала):

Старый дедовские Git Flow
Адекватный Trunk Base

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

В Git Flow, есть ветка dev, ветка master и ветки(feature/bug/tech). Разработка ведется в ветках feature, которые начинаются всегда от dev. В ветке feature мы разрабатываем фичу, затем на этой же ветке ее тестируем и после сливаем в dev. В момент релиза мы из ветки dev делаем ветку release, проводим всякие регрессы, делаем небольшие фиксы, если нашли баги. Затем мержим ветку release в master и в dev. При этом в момент мержа в мастер ставим tag с версией релиза. Чтобы в случае чего мы могли быстро к нему откатится и все такое. 

Проблемы у подхода в том, что он излишне формальный. Куча движений нужно как на релиз, так и на простую задачу. Git Flow не выдерживает большого количества людей в команде. Долгоживущие ветки, вероятно будут приводить к конфликтам, которые придется часто разруливать. Код ревью таких веток может и будет длиться вечность.

Этот подход хорош тогда, когда у нас продукт в котором очень важно выпустить очень сильно протестированное ПО. Или у нас вообще сложная доставка нашего ПО, вроде того как раньше распространяли все через диски. В веб разработке и в частности в мобильной ПО уже давно доставляется через инет.
👍20🔥1
{2/2} Trunk Base изначально может показаться странной системой ветвления, особенно для тех кто не работал в больших продуктах, или работал только по гитфлоу. В чем тут суть, у нас всего 2 ветки, это master и feature. Происходит работа следующим образом. Когда начинаем делать новую фичу, то делаем feature ветку из master и делаем очень маленькое изменение. Например, просто добавляем новый фрагмент или Activity. Этот кусок мы сразу мержим, т.к он мелкий и его крайне легко ревьювить. Затем уже отдельной веткой делаем UI, накидываем верстку, некоторые базовые анимации, затем мержим и эту ветку. Затем делаем логику отдельной веткой, ну думаю суть вы уловили. Тут может возникнуть вопрос, а как можно релизить половину фичи? Для этого мы всегда делаем систему фича тоглов.

Фиче тогл – система, которая позволяет удаленно, выключить и включить фичу в уже зарелиженой версии приложения. Бонусом мы получаем систему A/B тестов, когда например включаем фичу только на половине пользователей. Еще можно выкатывать приложение постепенно, не сразу на всех, а сначала проверить на 5% пользователей например. Каждую новую фичу мы просто покрываем вот таким фича тоглом, и просто не включаем фичи, которые еще не готовы к релизу. Код недоработанной фичи уже будет у клиента на устройстве, посто не будет работать)

Разумеется Trunk Base, накладывает некоторые ограничения на подходы к коду. Вроде того, что, в проекте должна быть хоть какая-то система тоглов, чтобы можно было закрывать фичи. Система не обязательно должна позволять включать и выключать фичи в проде, но должна быть возможно включать и выключать их на сборке для QA. Помимо этого, не обязательно, но очень желательно чтобы была хорошо настроенная система тестирования, ведь в ветке master должен быть всегда рабочий код готовый к релизу.

Gitflow появился на свет в 2010 году. За последние 13 лет индустрия сильно изменилась, уже никто не поставляет ПО на носителях, у нас давно любая программа скачивает обновления сама и сама себя обновляет. Сейчас на рынке выигрывает тот, кто тупо чаще может релизиться. И транк бэйз очень сильно помогает в том, чтобы релизиться чаще. 

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

И меня просто нереально начинает бомбить, когда в 2023 году, какие-то команды заявляют, что они делают приложения под мобилки и при этом у них Git flow. Перестаньте его использовать, в веб разработке Git flow дичайше устарел, это каргокультрая дичь, которая только тормозит разработку.
👍30👎52
Что за goAsync в Broadcast receiver?
😁25🔥5
Спорим я смогу рассказать одну штуку про Broadcast Receiver (BR), о которой с большой долей вероятности вы могли не знать? Как вы знаете основное применение BR это слушать события в системе и как-то на них реагировать. Происходит какое-то событие, система создает процесс для вашего приложения, выполняется Application.onCreate, после вызывается метод onReceive у нужного BR и собственно все.

В фоне вы ничего не сможете сделать, начиная с 10 Android запретили показывать Activity если приложение не видно пользователю. Теперь, даже если произошло что-то прикольное, максимум, что вы сможете сделать это показать уведомление. 

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

Как вы знаете, методы BR вызываются на UI потоке, благодаря чему просто синхронным вызовом это решить нельзя. И даже если это обойти, то если метод не ответит за 10 секунд, система нас прибьет. При этом если просто запустить другой Thread, после завершения метода onReceive система тупо убьет ваш процесс. Вот был бы способ, задержать процесс после выхода из метода. И такой способ есть, причем очень даже легальный. 

Представляю вашему вниманию метод – goAsync. Вот как это работает. Метод возвращает объект PendingResult, у которого есть метод finish. После использования метода goAsync, система убьет ваш процесс, только после того, как вы вызовете у PendingResult метод finish. Получаете PendingResult, создаете новый трэд, и передаете туда PendingResult. Далее делаете долгие походы в сеть и затем просто вызываете finish  у PendingResult.  Примерно вот так:

override fun onReceive(context: Context, intent: Intent) {
    val pendingResult = goAsync()
    thread {
        // getApiResults()
        pendingResult.finish()
    }
}


Разумеется было бы странно если бы у этого метода не было ограничений по времени. На все про все у вас будет 30 секунд, если не уложитесь, система не будет разбираться.
👍1021
Сейчас я напишу довольно странную на первый взгляд вещь, но мы в работе все меньше и меньше пишем код в парадигме ООП. Для начала пару слов про парадигмы. Великий Брагилевский пару лет назад написал довольно интересную вещь: “Нет никаких парадигм уже давно, не стоит кичиться знанием слов.” Более подробную эту тему он раскрыл в одном из выпусков подлодки (к сожалению не помню в каком точно).

Суть в чем, раньше понятие парадигма имело смысл. Языки делались с упором на ту или иную парадигму. Аля Java – ООП, Хаскель – функциональное, С – процедурное. Однако сейчас в 2023 году, такая вещь как парадигма уже устарела, т.к сейчас большинство популярных языков мультипарадигменные. Можно довольно просто писать в ООП стиле на Python, и в процедурном на kotlin. 

Все это предисловие вот к чему. Парадигма ООП говорит нам о том, что мы объединяем данные и методы их обработки в некоторые объекты. Другими словами, у нас у каждого объекта есть состояние, которое мы меняем вызывая методы этого объекта. Парадигма же функционального стиля напротив, говорит о том, что нужно избегать состояния и вообще изменяемых переменных.

Посмотрите на все лучшие практики которые мы стараемся использовать в коде. Мы делаем всю бизнес логику в интеракторах или репозиториях у которых нет состояния. Все для чего нужен объект это именно конструктор, в который мы просто передаем набор интерфейсов. И если какое-то состояние где-то и появляется (в 90% случаем это какой-то InMemory Cache), то оно скрыто за интерфейсом и выглядит просто как набор функций. Все сущности для бизнес логики просто берут данные откуда-то, что-то с ними делают и передают дальше. Ничего не напоминает? 

Причем это даже не ограничивается бизнес логикой. Посмотрите на UI, ради которого ООП в принципе и создавался. Все новые UI фреймворки: SwiftUI, Compose, React говорят о подходе описания UI через функции. Единственное место где у нас есть состояние это State в MVI, от которого мы пытаемся убежать, спрятав функционал работы со State в отдельную библиотеку. Да и даже если на чистый MVVM посмотреть, там тоже нет такого понятия как состояние, там же все реактивное, мы подписываемся на данные в модели, затем как-то их обрабатываем и сразу отправляем на UI через LiveData или Flow. 

Объекты мы используем только как данные, по сути как структуры в C. Причем у нас буквально в Kotlin есть пометка, что класс только для данных – data class. Он конечно не запрещает делать внутри меняемое состояние, но так делать моветон.

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

При этом, я разумеется не говорю, что мы пишем чисто в функциональном стиле. Я уже говорил, что чисто функциональный стиль не годится для промышленной разработки. Мы просто отобрали лучшее из ООП  – полиморфизм и некоторые паттерны GOF, из функционального – идея чистых функций и неизменяемости объектов, смешали все это в коктейль и называем Best Practice.

Поэтому как мне кажется в пору на собесе у джунов спрашивать не про ООП, а больше про функциональные подходы.
👍77🔥9😁2🤔1
Одна из самых каргокультных вещей в разработке это тесты. Мало кто говорит про действительно хорошие практики в тестировании и реально крутые идеи не получают особого распространения. Говорить про написание полезных тестов гораздо скучнее, чем холиварить об архитектуре, ведь тут приходится думать. В инфополе все какбудто сводится к двум идеям: покрывайте все юнитами (как дед завещал) и пишите тесты перед кодом. Реально, я постарался загуглить доклады про тестирование и они делятся на два типа: либо очередной рассказ про то как правильно все мокать в юнитах, либо как избавится от флаков на UI тестах. Создается впечатление, что новых идей тут нет.

Однако они есть, правда скрыты в локальных митапах и кулуарах. Сейчас на работе у меня большая часть задач связаны с тестами. Насколько блять большая, что я уже больше инфраструктурный тестировщик нежели разраб. Зато у меня есть что рассказать вам, про тесты. В этой серии поговорим о:

👉 Сложности в понимании типов тестирования
👉 Почему пирамида тестирования устарела?
👉 Действительно TDD работает, или это как коммунизм?
👉 Как еще можно обеспечить качество без тестов?
👉 Несколько идей о том, как писать тесты в Android?
🔥64🥰16👍142😁1
Первый пост чисто для разогрева, тут ничего супер нового, однако я обозначу пару проблем с пониманием типов тестирования.

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

Начну я вот с какой проблемы, у нас нет четкого понимания, в чем отличие разных типов тестов? Не спешите токсить в коментах, сначала дочитайте что бы понять, что я имею в виду)

По пирамиде тестирования есть 4 типа тестов:
👉 end-to-end
👉 системные
👉 интеграционные
👉 юниты

С end-to-end тестами вроде как все понятно. Поднимаем все приложение, библиотеку или что мы там разрабатываем, которая работает в условиях очень близко к продовым. А вот остальные 3 это котел холивара. Юнит тесты мы пишем вроде как на один модуль или класс. Интеграционные тесты затрагивают несколько компонентов, классов, модулей и т.д. Системные это вроде как что-то между интеграционным и end-to-end. Даже из описания системного теста появляется вот какая проблема.

Смотрите, я делаю класс A. Затем пишу тесты только на этот класс. Казалось бы это юнит тест. Затем в этом классе A из-за сложности, я выношу часть функционала в другой класс B. Класс A использует код капотом класс B. И вот юнит тесты которые я написал на первый класс А это все еще юнит тесты или уже интеграционные, ведь вроде как уже несколько компонентов?

Еще можно на юнит тесты посмотреть с другой стороны, они запускают кучу процессов под капотом, это и jvm и загрузка классов и выделение памяти. Если думать про это в таком ключе то даже самый простой юнит тест на самом деле системный.

Эта баллада к тому, что не существует абсолютной шкалы или разделения тестов. То что для одного проекта будет интеграционным тестом, для другого будет просто юнитом и наоборот. Например если мы делаем какую-то библиотеку, то в ней end-to-end тестом может быть просто проверка вызова метода какого-то системного API.

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

Другими словами когда мы не думаем о том, юнит это тест или интеграционный, а когда мы сосредотачиваемся на разделении, тестов на условно тесты для ViewModel и на тесты для Interactor. Помимо этого если мы говорим конкретно о тестах на Android, стоит больше уделять внимания разделению на тесты которые гоняются на эмуляторе и тесты которые можем прогнать локально.

Короче, как бы это не звучало банально, все сводится к старому доброму “все относительно“ и не слушайте фанатиков пирамиды.
18👍12🔥6👏5🤔3👎1
2025/07/09 00:09:37
Back to Top
HTML Embed Code: