Dev Easy Notes
3.17K subscribers
123 photos
1 video
147 links
Работаю в IT уже 8 лет. Рассказываю про разработку простым языком. Полезность скрыта под тупыми шутками и слоем мата. Лучший underground канал про разработку, который вы сможете найти.

По сотрудничеству писать @haroncode
Download Telegram
Если вы думали что я пропал, то нет, я просто хренову тучу времени убил на эксперименты с Content Provider, чтобы вам тут не напиздеть не ввести в заблуждение. Теперь у меня есть куча всего интересного чего я могу рассказать, что будет интересно даже прожженными сеньорам.

Content Provider я выделил отдельно вот по какой причине. У него в отличие от других компонентов есть несколько интересных моментов касательно установки разрешения. Так как Content Provider это основной компонент на него, как и на другие можно навесить обычное разрешение. Устанавливаем такое, и теперь только приложение с нужным разрешением сможет получить от нас какие-либо данные. Помимо этого можно exported сделать false и Content Provider получится приватным и только ваше приложение сможет его использовать. Тут все как у всех других компонентов ничего интересного.

Помимо обычного разрешения Content Provider позволяет настроить более точечную настройку разрешений. Есть возможность поставить разрешение, отдельно на запись и отдельно на чтение данных. Например, можно не задавать разрешение на чтение данных и позволить любому приложению читать данные, и при этом поставить signature разрешение на запись данных, тем самым разрешив модифицировать данные только определенным приложениям. Тут тоже все понятно и очевидно.

И самое интересное и запутанная хрень в Content Provider это grantUriPermissions. Лучше всего объяснить на примере. Смотрите, возможно вам когда-либо выпадала задача показать pdf файл в любом другом приложении. Этот файл доступен только вашему приложению, т.е он не лежит в открытой публичной папке. И вот каким образом дать разрешение другому приложению конкретно вот на этот файл?

Если вы пойдете на stackoverflow, там будет дикий запад с костылями про то, как это сделать. Поискав чуть лучше, можно будет найти FileProvider. Суть проста, FileProvider это частный случай Content Provider который работает только с файлами. А нужен он для того, чтобы вы могли раздавать свои приватные файлы другим приложениям. 

Идея тут должна быть понятна, мы не можем перегнать весь файл в bundle и отправить его в другое приложение, мы можем другому приложению передать только путь до нашего файла. Однако, что другое приложение сможет сделать с этим путем? Да ничего, система пошлет его лесом как только он попробует по этому пути перейти. А вот FileProvider позволяет легально обойти это ограничение. 

Если вы зайдете в доку FileProvider, там будет сказано, что вам нужно сделать этот провайдер как exported=false. И тут внимательный читатель задастся вопросом: “Если exported=false то как он вообще что-то может передать другому приложению, ведь провайдер становится приватным“. Все так и есть, FileProvider делается приватным, однако мы также указываем grantUriPermissions=true и вот тут, начинается самое интересное. 

GrantUriPermissions это настройка, которая позволяет другим приложениям выдавать временный доступ до конкретного Uri (если говорим про FileProvider то условно путь до файла). Однако работает она не сама по себе, мы должны явно сообщить об этом системе, и тут два пути:
👉 Первый это когда мы формируем Intent на показ файла, нужно добавить флаг ‘FLAG_GRANT_READ_URI_PERMISSION’  и приложение, которое получит этот Intent получить также право временно(до перезагрузки устройства) обращаться к переданному Uri(к нашему файлу). 
👉 Второй, мы можем программно вызывать метод grantUriPermission, передать в него наш Uri а также пакет конкретного приложения, которому мы это права даем. Ну и есть зеркальный метод revokeUriPermission, который это разрешение отзывает.

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

Однако это не все самое прикольное. Я обнаружил интересную толи багу, толи фичу связанную с FLAG_GRANT_READ_URI_PERMISSION, о которой расскажу в следующем посте.
🔥58👍20
Интересное поведение ACTION_SEND

👇
🔥5
В прошлом посте я описал штуку под названием FLAG_GRANT_READ_URI_PERMISSION, которая позволяет выдать временное разрешение на какой-либо файл когда его нужно зашарить с другим приложением. Я разбирал его на примере кейса, когда нам нужно просто открыть файл. Создаем Intent с ACTION_VIEW, добавляем Uri, закидываем флаг для прав и погнали. Все работает достаточно логично и очевидно, однако есть другой action, который творит магию, которая мне не доступна для понимания. 

Вот смотрите, такой кейс: нам нужно не просто показать файл, а отправить его по email. Для этого в Android есть специальный action ACTION_SEND. В него можно также передать данные о том, на какой email мы отправляем сообщение, задать тему, текст и вот это все. Самое интересное, что можно также прикрепить файл для отправки. Как вы помните мы не можем просто взять и передать в Intent ссылку на файл, нужно еще проставить FLAG_GRANT_READ_URI_PERMISSION.

Когда я проверял поведение, решил, что, ну надо же проверить как будет вести себя система без передачи этого флага. Обнаружил весьма забавную вещь. Если в Intent с ACTION_SEND передаем Uri на файл, разрешение выдается автоматически. Серьезно я пробовал это и на эмуляторе и на реальном устройстве. Все работает без указания флага FLAG_GRANT_READ_URI_PERMISSION. И так работает только с ACTION_SEND, в кейсе с ACTION_VIEW, если самому не выдать разрешение, другое приложение ничего не откроет.

Для проверки я накидал мини приложеньку, которая просто перехватывает Intent с ACTION_SEND и действительно, можно просто так получить файл. Однако если при этом приложение попытается позже в другой Activity обратиться к этому Uri, система пошлет приложение лесом. 

Вывод какой, вероятно где-то под капотом, если вы передаете Intent с ACTION_SEND и кладете в него Uri, система к этому Intent проставляет разрешение на чтение. Видимо это из соображение бизнес логики. Если вы даете такой функционал пользователю, значит вы точно хотите расшарить файл и система любезно проставляет разрешения за вас. Спорный момент, как мне кажется было бы очевиднее если бы такими флагами мы управляли сами. Ну а по пока получается, что ACTION_SEND это аналог сына депутата, который может работать и без прав.

Также в инете по всюду пишут, что разрешение FLAG_GRANT_READ_URI_PERMISSION выдается до тех пор пока не вызывали revokeUriPermission либо пока устройство не перезагрузится. Это не совсем правда, возможно так было на раних версиях Android. Сейчас же я тестировал на Android 11+ и там разрешение выдается исключительно на время жизни Intent который мы передали, т.е пока живет та Activity которая открылась этим Intent.

В итоге, даже несмотря на всю эту магию, если у вас есть функциональность отправки email с файлов, я бы советовал проставлять вручную флаг FLAG_GRANT_READ_URI_PERMISSION, чисто на всякий случай, мало ли как там другие прошивки работают.
👍28🤔3
Как раздаем права в UI тестах?

👇
🔥4
Теперь совсем немного поговорим о том, как раздавать права в тестах. Смотрите, когда мы говорим про разрешения в тестах, очень вероятно речь идет именно о 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 лайков под постом)
👍86🔥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