Меня всегда удивляло как легаси превратили в маркетинговый инструмент для вакансии. Очень часто можно увидеть фразу у нас нет легаси, весь код написан на Kotlin. Возникает вопрос, а точно ли они понимают что такое легаси? Почему если фича написана на Java то это сразу легаси, а может ли быть легаси на Kotlin? Складывается впечатление, что если мы пишем на Kotlin то это по определению код который легаси быть не может.
Представьте ситуацию, есть крутая команда разработчиков. Они придерживаются лучших практик, уделяют внимание архитектуре, чистоте кода, тестам вся фигня, но при этом пишут все на Java. Затем в индустрии все начинают переходить на Kotlin. Переходят на него по понятным причинам, он позволяет избежать многих ошибок, писать на нем приятнее и при этом все плюсы JVM сохраняются.
В этот момент весь код на Java нарекают легаси. Возникает вопрос, схерали? Да в этом коде нет фишек которые есть у Kotlin, но код по-прежнему чистый, поддерживаемый и покрыт тестами в разумных пределах. Его нарекли легаси только потому, что он не написан на другом более новом языке. Однажды наступит момент, когда на смену Kotlin придет еще более совершенный язык. Тогда весь на код на Kotlin станет легаси?
Если задуматься то какой код можно назвать легаси? Мне нравится определение Майкла Физерса: “легаси код – код который не проходит тесты“. В продукте всегда происходит куча изменений, изменения в архитектуре, подходах, требованиях. Это нормально так и должно быть. Бизнес может менять стратегию, а следовательно и требования. Если бизнес не меняется и не растет, то конкуренты просто загребут весь рынок. Кодовая база при этом должна поддерживать эти изменения, архитектура должна помогать в этом.
Код становится легаси в том случае, когда внесения изменений в него становится страшно дорогим. Если у кода, нет никаких инструментов тестирования и внятной архитектуры ты не можешь безопасно вносить изменения. Ты не уверен в том, не сломал ли ты чего. Приложение при этом может быть громадным, протестировать все будет очень дорого для команды. Появляется страх изменений и появляются выражения: “работает не трогает”.
Легаси не зависит от языка на котором писали. Если есть хорошее тестирование, код на старом языке можно понемногу переписывать на новый, и даже без переписывания безопасно вносить изменения. Отсюда также выходит другая интересная вещь. Даже новый код, написанный на новом языке, может стать легаси, если у него нет никаких тестов и выбрана архитектура которая не дает легко изменять код.
Поэтому забавно когда пытаются продать проект, написанный полностью на Kotlin с утверждением, что “У нас вообще нет легаси”. Камон ребята, пара итераций и это легаси появится. Поэтому не ведитесь на этот маркетинговый ход, задавайте кучу вопросов на собесе.
Представьте ситуацию, есть крутая команда разработчиков. Они придерживаются лучших практик, уделяют внимание архитектуре, чистоте кода, тестам вся фигня, но при этом пишут все на Java. Затем в индустрии все начинают переходить на Kotlin. Переходят на него по понятным причинам, он позволяет избежать многих ошибок, писать на нем приятнее и при этом все плюсы JVM сохраняются.
В этот момент весь код на Java нарекают легаси. Возникает вопрос, схерали? Да в этом коде нет фишек которые есть у Kotlin, но код по-прежнему чистый, поддерживаемый и покрыт тестами в разумных пределах. Его нарекли легаси только потому, что он не написан на другом более новом языке. Однажды наступит момент, когда на смену Kotlin придет еще более совершенный язык. Тогда весь на код на Kotlin станет легаси?
Если задуматься то какой код можно назвать легаси? Мне нравится определение Майкла Физерса: “легаси код – код который не проходит тесты“. В продукте всегда происходит куча изменений, изменения в архитектуре, подходах, требованиях. Это нормально так и должно быть. Бизнес может менять стратегию, а следовательно и требования. Если бизнес не меняется и не растет, то конкуренты просто загребут весь рынок. Кодовая база при этом должна поддерживать эти изменения, архитектура должна помогать в этом.
Код становится легаси в том случае, когда внесения изменений в него становится страшно дорогим. Если у кода, нет никаких инструментов тестирования и внятной архитектуры ты не можешь безопасно вносить изменения. Ты не уверен в том, не сломал ли ты чего. Приложение при этом может быть громадным, протестировать все будет очень дорого для команды. Появляется страх изменений и появляются выражения: “работает не трогает”.
Легаси не зависит от языка на котором писали. Если есть хорошее тестирование, код на старом языке можно понемногу переписывать на новый, и даже без переписывания безопасно вносить изменения. Отсюда также выходит другая интересная вещь. Даже новый код, написанный на новом языке, может стать легаси, если у него нет никаких тестов и выбрана архитектура которая не дает легко изменять код.
Поэтому забавно когда пытаются продать проект, написанный полностью на Kotlin с утверждением, что “У нас вообще нет легаси”. Камон ребята, пара итераций и это легаси появится. Поэтому не ведитесь на этот маркетинговый ход, задавайте кучу вопросов на собесе.
🔥34👍9❤2
Утечка памяти наверное одна и самых неприятных ошибок которую можно совершить при разработке. Она может привести к лагам на устройстве и порой даже крэшу. В предыдущих постах я упоминал, что сделать утечку памяти можно даже не подозревая об этом. Насколько бы вы внимательно не относились к коду из-за человеческого фактора такая ошибка рано или поздно всплывет. Причем утечка памяти может быть даже в библиотеках от гугла.
Естественно эту проблему нужно было как-то решать. Поэтому был создан инструмент, который позволяет находить утечки памяти во время тестирования работы приложения. Возможно вы уже натыкались на этот инструмент – LeakCanary. Библиотека максимальна проста в использовании при этом позволяет быстро найти корень проблемы. LeakCanary следит за приложением, проводит анализ в фоне, сохраняет результаты, даже строит путь до ссылки из-за которой произошла утечка.
При всем при этом, со стороны клиента ничего не нужно делать. Просто указал либу в зависимостях gradle, и она сама начинает работать. Естественно инженерное любопытство заставляет задаться вопрос, а как работает эта технология?
Я планировал сделать один пост по тому, как работает leak canary, но в ней перемешано столько интересных технологий, что я решил разбить на несколько постов. Каждый их которых будет разбирать отдельную часть библиотеки и отвечать на конкретный вопрос:
👉 Как она запускается, хотя мы в коде ничего не прописывали?
👉 Откуда берется отдельный ярлык, который позволяет посмотреть историю всех утечек конкретного приложения?
👉 Как leak canary вообще находит утечки?
👉 Как находится ссылка из-за которой произошла утечка?
Естественно эту проблему нужно было как-то решать. Поэтому был создан инструмент, который позволяет находить утечки памяти во время тестирования работы приложения. Возможно вы уже натыкались на этот инструмент – LeakCanary. Библиотека максимальна проста в использовании при этом позволяет быстро найти корень проблемы. LeakCanary следит за приложением, проводит анализ в фоне, сохраняет результаты, даже строит путь до ссылки из-за которой произошла утечка.
При всем при этом, со стороны клиента ничего не нужно делать. Просто указал либу в зависимостях gradle, и она сама начинает работать. Естественно инженерное любопытство заставляет задаться вопрос, а как работает эта технология?
Я планировал сделать один пост по тому, как работает leak canary, но в ней перемешано столько интересных технологий, что я решил разбить на несколько постов. Каждый их которых будет разбирать отдельную часть библиотеки и отвечать на конкретный вопрос:
👉 Как она запускается, хотя мы в коде ничего не прописывали?
👉 Откуда берется отдельный ярлык, который позволяет посмотреть историю всех утечек конкретного приложения?
👉 Как leak canary вообще находит утечки?
👉 Как находится ссылка из-за которой произошла утечка?
🔥36👍10❤1
В каждом Android приложении есть такой файл AndroidManifest. В манифесте мы прописываем основные компоненты нашего приложения. Делаем мы это для того, чтобы показать системе какие компоненты у нас есть, какие события мы хотим отлавливать, какие разрешения нам нужны и еще дофига всего. Нужен он для того, чтобы система понимала, что наше приложение умеет и какие данные может предоставить.
Приложение может состоять из многих модулей. Соответственно в каждом модуле будет определен свой AndroidManifest, в нем будут описываться компоненты, которые используются в данном модуле. Помимо этого вы можете подключать некоторые библиотеки, в которых могут быть свои Activity, Service и т.д. У этой библиотеки также будет свой AndroidManifest.
К чем я все это веду? Когда вы собираете ваше приложение, компилятор мержит все эти манифесты в один большой манифест. Потому как в конечном архиве(apk) система ожидает увидеть только один манифест. Нужно понимать этот механизм.
Теперь поговорим об основных компонентах приложения. Среди них есть один, используется он реже всего, но позволяет делать интересные штуки. Есть такой компонент Content Provider, предназначается он для данных между приложениями. Передача данных нас сейчас не интересует, а интересует его фишки.
Во-первых,
Другими словами, если вы в своей либе создадите манифест, в котором укажете свой Content Provider, он будет запущен до старта приложения. Это дает возможность словить момент запуска приложения и даже получить контекст. При этом не нужно ничего нигде прописывать, система сама создаст Content Provider и дернет метод onCreate. Из этого получаем два вывода.
Вывод номер рас. Нужно проверять код незнакомых библиотек. В одной из них может оказаться вот такой Content Provider который безнаказанно стырит данные пользователя и отправит их на левый сервер.
Вывод номер два. Можно прикрутить функциональность ничего не прописывая в коде. Именно этот механизм и использует LeakCanary. Библиотека просто подсовывает свой Content Provider, тем самым отлавливает момент запуска приложения. Ну а получив доступ к Context, LeakCanary получает доступ практически ко всему приложению. Она навешивает кучу листнеров которые позволяют отлеживать все Acivity, Fragment, Service и т.д.
P.S По этой же схеме работают некоторые библиотеки гугла, вроде Firebase.
Приложение может состоять из многих модулей. Соответственно в каждом модуле будет определен свой AndroidManifest, в нем будут описываться компоненты, которые используются в данном модуле. Помимо этого вы можете подключать некоторые библиотеки, в которых могут быть свои Activity, Service и т.д. У этой библиотеки также будет свой AndroidManifest.
К чем я все это веду? Когда вы собираете ваше приложение, компилятор мержит все эти манифесты в один большой манифест. Потому как в конечном архиве(apk) система ожидает увидеть только один манифест. Нужно понимать этот механизм.
Теперь поговорим об основных компонентах приложения. Среди них есть один, используется он реже всего, но позволяет делать интересные штуки. Есть такой компонент Content Provider, предназначается он для данных между приложениями. Передача данных нас сейчас не интересует, а интересует его фишки.
Во-первых,
onCreate
у Content Provider вызывается перед onCreate
у Application, из-за этого Content Provider часто используют для какой-нибудь аналитики, которую нужно настроить еще до запуска самого приложения. Во вторых это единственный компонент приложения который создается в момент старта приложение, даже чуть раньше. Другими словами, если вы в своей либе создадите манифест, в котором укажете свой Content Provider, он будет запущен до старта приложения. Это дает возможность словить момент запуска приложения и даже получить контекст. При этом не нужно ничего нигде прописывать, система сама создаст Content Provider и дернет метод onCreate. Из этого получаем два вывода.
Вывод номер рас. Нужно проверять код незнакомых библиотек. В одной из них может оказаться вот такой Content Provider который безнаказанно стырит данные пользователя и отправит их на левый сервер.
Вывод номер два. Можно прикрутить функциональность ничего не прописывая в коде. Именно этот механизм и использует LeakCanary. Библиотека просто подсовывает свой Content Provider, тем самым отлавливает момент запуска приложения. Ну а получив доступ к Context, LeakCanary получает доступ практически ко всему приложению. Она навешивает кучу листнеров которые позволяют отлеживать все Acivity, Fragment, Service и т.д.
P.S По этой же схеме работают некоторые библиотеки гугла, вроде Firebase.
❤36👍20🔥2
С этим пунктом в LeakCanary все еще проще. Как вообще мы указываем системе какую Activity нужно запустить первой? Опять-таки через AndroidManifest и специальные intent-filter которые указываем у Activity. В intent-filter мы прописываем Action показывающий на какие действия система должна предлагать эту Activity и Category показывающая системе дополнительную инфу где располагать эту Activity.
Для главной Activity Action = android.intent.action.MAIN, Category = android.intent.category.LAUNCHER. Система читает этот Manifest и исходя из этих action и category понимает, что эту Activity нужно отобразить в лаунчере. Интересный момент заключается в том, что таких Activity может быть много. У вас есть возможность сделать хоть 3 разных точек входа в приложения причем с разными иконками и разными подписями.
LeakCanary в своем манифесте подсовывает такую Activity. При нажатии на эту Activity просто открывается не главная Activity вашего приложения, а вот эта Activity библиотеки которая позволяет получить данные об утечках этого приложения. Другими словами, помимо специального Content Provider, библиотека подсовывает вам еще и свои Activity (их там несколько).
Application при этом только один, а значит каждая такая Activity будет привязана именно к конкретному приложению, это позволяет избежать путаницы когда у вас два разных приложения в которых используется LeakCanary.
Очевидно что остается не очень удобное поведение когда мы сначала запустили Activity LeakCanary, а затем запустили Activity уже нашего приложения. Неудобство тут в том, что не понятно что делать с навигацией, т.к это вроде две отдельные части приложения которые не должны быть вместе.
Чтобы убрать это неудобство, используется taskAffinity. Если не знаете или забыли давайте вспомним. Activity у нас запускаются в стэке, который чем-то напоминает стэк фрагментов. Этих стэков у приложения может быть несколько. По дефолту все Activity запускаются в одном стэке. Однако у Activity есть специальный атрибут который позволяет указать в каком стеке должна запускаться Activity.
Этот атрибут taskAffinity. Прописываем какую-то уникальную строку в этом атрибуте желательно чтобы в этой строке был ваш applicationId чтобы не было путаницы в другими приложениями. После этого Activity будет запускаться не в стандартном стэке, а в другом. В лаунчере со списком запущенных приложений эти стэки будут разными, т.е у вас будет как будто бы два отдельных приложения, хотя на самом деле одно.
LeakCanary используют taskAffinity для своих Activity чтобы не влезать в навигацию вашего приложения. Это позволяет сделать полную видимость того, что у вас в одном приложении два. Первое основное и второе которое связано исключительно с информацией про утечки.
Для главной Activity Action = android.intent.action.MAIN, Category = android.intent.category.LAUNCHER. Система читает этот Manifest и исходя из этих action и category понимает, что эту Activity нужно отобразить в лаунчере. Интересный момент заключается в том, что таких Activity может быть много. У вас есть возможность сделать хоть 3 разных точек входа в приложения причем с разными иконками и разными подписями.
LeakCanary в своем манифесте подсовывает такую Activity. При нажатии на эту Activity просто открывается не главная Activity вашего приложения, а вот эта Activity библиотеки которая позволяет получить данные об утечках этого приложения. Другими словами, помимо специального Content Provider, библиотека подсовывает вам еще и свои Activity (их там несколько).
Application при этом только один, а значит каждая такая Activity будет привязана именно к конкретному приложению, это позволяет избежать путаницы когда у вас два разных приложения в которых используется LeakCanary.
Очевидно что остается не очень удобное поведение когда мы сначала запустили Activity LeakCanary, а затем запустили Activity уже нашего приложения. Неудобство тут в том, что не понятно что делать с навигацией, т.к это вроде две отдельные части приложения которые не должны быть вместе.
Чтобы убрать это неудобство, используется taskAffinity. Если не знаете или забыли давайте вспомним. Activity у нас запускаются в стэке, который чем-то напоминает стэк фрагментов. Этих стэков у приложения может быть несколько. По дефолту все Activity запускаются в одном стэке. Однако у Activity есть специальный атрибут который позволяет указать в каком стеке должна запускаться Activity.
Этот атрибут taskAffinity. Прописываем какую-то уникальную строку в этом атрибуте желательно чтобы в этой строке был ваш applicationId чтобы не было путаницы в другими приложениями. После этого Activity будет запускаться не в стандартном стэке, а в другом. В лаунчере со списком запущенных приложений эти стэки будут разными, т.е у вас будет как будто бы два отдельных приложения, хотя на самом деле одно.
LeakCanary используют taskAffinity для своих Activity чтобы не влезать в навигацию вашего приложения. Это позволяет сделать полную видимость того, что у вас в одном приложении два. Первое основное и второе которое связано исключительно с информацией про утечки.
👍29❤1🔥1
В основе механизма лежит простая идея. Чтобы понять эту идею, достаточно знать типы ссылок. Да те самые типы, которые в большинстве случаев только на собесе и упоминаются. Есть 4 типа ссылок в Java, нас сейчас интересует только 2: сильные (Strong Reference) и слабые (Weak Reference).
С сильными ссылками все просто, пока эта ссылка существует где-то, GC точно не удалит этот объект, который к этой ссылке привязан. Слабые ссылки в таком кейсе не гарантируют сохранение объекта. Другими словами вы создали объект, потом положили его в слабую ссылку, теперь у вас только слабая ссылка. Когда вам понадобится это объект, в ссылке может оказаться просто null. Если GC решит что памяти мало он просто удалит объекты привязанные к слабым ссылкам.
Дальше еще один факт. Если у нас есть одновременно и слабая ссылка и сильная на объект, то GC не будет удалять этот объект при нехватке памяти, а также не разорвет связь между слабой ссылкой и объектом.
Возвращаясь к работе LeakCanary. Разберем просто как отлеживаются утечки в Activity. Во фрагментах, View, сервисах и т.д работает тот же принцип. Сначала библиотека вешает на context приложения специальный листенер который позволяет отслеживать момент, когда любая Activity умирает.
Перехватив момент когда Activity умирает, LeakCanary оборачивает эту Activity в слабую ссылку и сохраняет у себя. Затем сразу запускает GC, точнее сказать рекомендует JVM запустить GC.
После какого-то времени, библиотека смотрит обнулилась ли ссылка. Если обнулилась значит все ок, никакой утечки не было. Если ссылка по-прежнему не null, значит где-то еще есть сильная ссылка, что означает утечку.
С сильными ссылками все просто, пока эта ссылка существует где-то, GC точно не удалит этот объект, который к этой ссылке привязан. Слабые ссылки в таком кейсе не гарантируют сохранение объекта. Другими словами вы создали объект, потом положили его в слабую ссылку, теперь у вас только слабая ссылка. Когда вам понадобится это объект, в ссылке может оказаться просто null. Если GC решит что памяти мало он просто удалит объекты привязанные к слабым ссылкам.
Дальше еще один факт. Если у нас есть одновременно и слабая ссылка и сильная на объект, то GC не будет удалять этот объект при нехватке памяти, а также не разорвет связь между слабой ссылкой и объектом.
Возвращаясь к работе LeakCanary. Разберем просто как отлеживаются утечки в Activity. Во фрагментах, View, сервисах и т.д работает тот же принцип. Сначала библиотека вешает на context приложения специальный листенер который позволяет отслеживать момент, когда любая Activity умирает.
Перехватив момент когда Activity умирает, LeakCanary оборачивает эту Activity в слабую ссылку и сохраняет у себя. Затем сразу запускает GC, точнее сказать рекомендует JVM запустить GC.
После какого-то времени, библиотека смотрит обнулилась ли ссылка. Если обнулилась значит все ок, никакой утечки не было. Если ссылка по-прежнему не null, значит где-то еще есть сильная ссылка, что означает утечку.
👍40🔥4❤2
LeakCanary начинает поиск пути к ссылке из-за которой произошла утечка с получения копии (dump) памяти. Любая jvm предоставляет такой функционал. Так мы получаем копию всех объектов памяти JVM в удобном формате чтобы можно было проводить анализ. Чтобы получить копию памяти в Android достаточно вызвать функцию
Итак мы получили файл, в котором лежит информация в всех объектах JVM в определенный момент времени. Дальше нужно как-то начать поиск утечки. У нас куча объектов и не особо понятно с чего вообще нужно начинать поиск. LeakCanary использует не обычные Weakreferece а свой подкласс KeyedWeakReference. В этом классе есть дополнительная инфа о том, ссылается ли эта ссылка на утёкший объект, или нет.
Чтобы дальше понимать реализацию нужно вспомнить что такое GC root? GC root это корни от которых тянутся все ссылки в heap. В частности это ссылки в стеке, потоки, статические ссылки, сlassloaders, думаю суть понятна.
Анализатор утечек в полученной копии памяти ищет объекты класса KeyedWeakReference. Затем просто по ссылке смотрит на какой объект они ссылаются. Таким образом мы находим утекший объект, это либо может быть Activity, View и т.д все что можно утечь. После того как мы нашли что за объект утек, нужно простроить путь до GC root чтобы найти ссылку из-за которой он утек. Для этого используется алгоритмы графов для поиска кротчайщего пути ииии на этом все.
Сама концепция довольно простая, но реализация это целый rocket sciencе который я тут не буду описывать, потому как сам этот алгоритм тянет на целый доклад. Серьезно, можете разобрать этот алгоритм и выступить, я бы сходил послушать)
Debug.dumpHprofData
. В эту функцию передаем путь к файлу, а дальше система все сделает за нас. Итак мы получили файл, в котором лежит информация в всех объектах JVM в определенный момент времени. Дальше нужно как-то начать поиск утечки. У нас куча объектов и не особо понятно с чего вообще нужно начинать поиск. LeakCanary использует не обычные Weakreferece а свой подкласс KeyedWeakReference. В этом классе есть дополнительная инфа о том, ссылается ли эта ссылка на утёкший объект, или нет.
Чтобы дальше понимать реализацию нужно вспомнить что такое GC root? GC root это корни от которых тянутся все ссылки в heap. В частности это ссылки в стеке, потоки, статические ссылки, сlassloaders, думаю суть понятна.
Анализатор утечек в полученной копии памяти ищет объекты класса KeyedWeakReference. Затем просто по ссылке смотрит на какой объект они ссылаются. Таким образом мы находим утекший объект, это либо может быть Activity, View и т.д все что можно утечь. После того как мы нашли что за объект утек, нужно простроить путь до GC root чтобы найти ссылку из-за которой он утек. Для этого используется алгоритмы графов для поиска кротчайщего пути ииии на этом все.
Сама концепция довольно простая, но реализация это целый rocket sciencе который я тут не буду описывать, потому как сам этот алгоритм тянет на целый доклад. Серьезно, можете разобрать этот алгоритм и выступить, я бы сходил послушать)
🔥22👍5❤3
Я решил сделать что-то вроде оглавления для удобства. Этот пост сборник постов которые я бы рекомендовал к прочтению перед собесами. Здесь основные темы, достаточно просто их перечитать и сможете ответить на большую часть вопросов которые прилетают на собеседовании.
– Как работает UI в android
– Все про фрагменты
– В чем разница м/у смертью Activity и смертью процесса?
– Проблемы многопоточности
– Разница между IO и Сomputation
– Основы функционального программирования
– Как работает UI в android
– Все про фрагменты
– В чем разница м/у смертью Activity и смертью процесса?
– Проблемы многопоточности
– Разница между IO и Сomputation
– Основы функционального программирования
👍39🔥28❤1
Начну с истории. Сидим мы как-то с коллегами в баре, и я завел разговор про compose. Описывал я его при помощи маркетинговых заголовков гугла, о том что в нем нужно меньше кода, он проще и удобнее, производительнее. На что получил вопрос: “ну вот, а конкретно, какие проблемы решаем compose?”. В тот вечер я их так и не убедил, и осадочек остался. Чтобы компенсировать этот пробел, давайте разберем, а зачем вообще compose появился, какие проблемы он решает, в чем вообще прикол декларативного UI?
Как обычно то что я планировал сделать одним постом, разрослось на два. Поэтому в первом посте разберем что вообще такое декларативный UI и немного истории, а уже во втором я постараюсь ответить на вопрос какие конкретно проблемы решает compose.
👉 История развития UI
👉 Какие проблемы решает Compose
👉 Проблемы Compose
Как обычно то что я планировал сделать одним постом, разрослось на два. Поэтому в первом посте разберем что вообще такое декларативный UI и немного истории, а уже во втором я постараюсь ответить на вопрос какие конкретно проблемы решает compose.
👉 История развития UI
👉 Какие проблемы решает Compose
👉 Проблемы Compose
🤔22👍6🥰4🔥2❤1
Давайте начнем с того, что поймем разницу м/у императивным и декларативным UI. Понять разницу можно легко через аналогию. Допустим вы хотите заказать сэндвич в кафе.
В императивном стиле это выглядит так: "Нужно взять белый батон, разрезать его пополам, затем намазать хлеб кетчупом, после нарезать колбасы и положить на батон, затем добавить сыр, нарезать помидор и положить на сыр, после добавить лук и накрыть его другом куском хлеба"
В декларативном стиле: "Хочу сэндвич с колбасой".
Из примера должно быть понятно что отличия в том, что в одном случае мы описываем как хотим получить результат, во втором случае описываем лишь результат, а система должна уже сам решить как его достигать.
Касательно интерфейсов индустрия прошла немалый путь. Изначально как вы помните интерфейсов как таковых не было, была командная строка и всем ее хватало. Затем начали появляться оконные системы потому как был запрос на удобный способ взаимодействия с компом без запоминания тысячи команд.
В этот же момент начали появляться объектно ориентированные языки программирования, и оказалось что довольно удобно при помощи объектов описывать UI элементы вроде кнопок, текста, чекбоксов и т.д.
Затем разработчики поняли что все равно приходится дублировать код, а еще приходится постоянно кучу раз перезапускать сборку, чтобы понять, а стоит ли кнопка в том месте в котором должна.
Появилась идея, давайте описывать UI в специальном формате, чтобы можно было, не пересобирая приложение, понять стоит ли кнопка там где нужно. Появились специальные языки разметки, в вэбе с которого все началось это html, в Android xml, в iOS xib и еще многие другие для разных платформ. Так или иначе мы пришли к тому, что делать интерфейсы из кода не удобно, охото иметь возможно быстро накидать верстку, проверить специальным инструментом для preview все ли стоит там где нужно, а уже потом прикручивать логику.
И вот тут казалось бы можно и остановится, ведь интерфейсы уже стали декларативными. Ведь мы уже не говорим как именно рисовать интерфейс, мы описываем просто результат который хотим получить при помощи верстки. Почему тогда декларативными называют только недавно вышедшие SwiftUI, Compose и React?
Основная проблема в том, что требования к интерфейсам со временем тоже поменялись. Если раньше нас устраивала статичная страница, то сейчас мы хотим красивые анимации, кучу плавных переходов, красивые всплывающие окна. Интерфейсы уже давно не статичные, они должны меняться в ответ на действия пользователя, все должно быть максимально интерактивно. Получается что у нас есть верстка, а также есть код который её меняет.
Появляется код в приложении который добавляет элементы, удаляет, что-то скрывает или анимирует. В этот момент наш UI перестает быть декларативным, потому как мы явно прописываем в коде, что конкретно нужно поменять. Вот тут сдвинуть кнопку, а вот эту перекрасить вот в такой цвет, а вот этот чекбокс сделать disable. Думаю суть вы уловили.
При всем при этом UI становится сложнее, появляется куча элементов которые уже не опишешь через язык вёрстки. Вспомните сколько нужно написать кода, чтобы показать список в котором будет еще список с горизонтальным скролом?
У каждой большой компании появляется своя выделенная команда, которая делает свои UI элементы. И вы только вдумайтесь, отдельная команда задача которой разрабатывать свои кнопки немного отличающиеся по поведению от стандартных. Сука кнопки!
Сложность появляется там, где ее по идее быть не должно. Эпоха языков вёрстки подходит к концу. Они уже давно не помогают решить проблемы которые у нас возникают. Технологии уже давно позволяют быстро компилировать участи кода, чтобы показывать preview верстки даже если она описана не через xml или html. Поэтому xml уже сдает позиции, да и языки исключительно для верстки тоже.
В императивном стиле это выглядит так: "Нужно взять белый батон, разрезать его пополам, затем намазать хлеб кетчупом, после нарезать колбасы и положить на батон, затем добавить сыр, нарезать помидор и положить на сыр, после добавить лук и накрыть его другом куском хлеба"
В декларативном стиле: "Хочу сэндвич с колбасой".
Из примера должно быть понятно что отличия в том, что в одном случае мы описываем как хотим получить результат, во втором случае описываем лишь результат, а система должна уже сам решить как его достигать.
Касательно интерфейсов индустрия прошла немалый путь. Изначально как вы помните интерфейсов как таковых не было, была командная строка и всем ее хватало. Затем начали появляться оконные системы потому как был запрос на удобный способ взаимодействия с компом без запоминания тысячи команд.
В этот же момент начали появляться объектно ориентированные языки программирования, и оказалось что довольно удобно при помощи объектов описывать UI элементы вроде кнопок, текста, чекбоксов и т.д.
Затем разработчики поняли что все равно приходится дублировать код, а еще приходится постоянно кучу раз перезапускать сборку, чтобы понять, а стоит ли кнопка в том месте в котором должна.
Появилась идея, давайте описывать UI в специальном формате, чтобы можно было, не пересобирая приложение, понять стоит ли кнопка там где нужно. Появились специальные языки разметки, в вэбе с которого все началось это html, в Android xml, в iOS xib и еще многие другие для разных платформ. Так или иначе мы пришли к тому, что делать интерфейсы из кода не удобно, охото иметь возможно быстро накидать верстку, проверить специальным инструментом для preview все ли стоит там где нужно, а уже потом прикручивать логику.
И вот тут казалось бы можно и остановится, ведь интерфейсы уже стали декларативными. Ведь мы уже не говорим как именно рисовать интерфейс, мы описываем просто результат который хотим получить при помощи верстки. Почему тогда декларативными называют только недавно вышедшие SwiftUI, Compose и React?
Основная проблема в том, что требования к интерфейсам со временем тоже поменялись. Если раньше нас устраивала статичная страница, то сейчас мы хотим красивые анимации, кучу плавных переходов, красивые всплывающие окна. Интерфейсы уже давно не статичные, они должны меняться в ответ на действия пользователя, все должно быть максимально интерактивно. Получается что у нас есть верстка, а также есть код который её меняет.
Появляется код в приложении который добавляет элементы, удаляет, что-то скрывает или анимирует. В этот момент наш UI перестает быть декларативным, потому как мы явно прописываем в коде, что конкретно нужно поменять. Вот тут сдвинуть кнопку, а вот эту перекрасить вот в такой цвет, а вот этот чекбокс сделать disable. Думаю суть вы уловили.
При всем при этом UI становится сложнее, появляется куча элементов которые уже не опишешь через язык вёрстки. Вспомните сколько нужно написать кода, чтобы показать список в котором будет еще список с горизонтальным скролом?
У каждой большой компании появляется своя выделенная команда, которая делает свои UI элементы. И вы только вдумайтесь, отдельная команда задача которой разрабатывать свои кнопки немного отличающиеся по поведению от стандартных. Сука кнопки!
Сложность появляется там, где ее по идее быть не должно. Эпоха языков вёрстки подходит к концу. Они уже давно не помогают решить проблемы которые у нас возникают. Технологии уже давно позволяют быстро компилировать участи кода, чтобы показывать preview верстки даже если она описана не через xml или html. Поэтому xml уже сдает позиции, да и языки исключительно для верстки тоже.
👍28❤8🔥7
Как я уже упоминал, в анализе технологий нужно всегда исходить из проблемы, которую они решают. Я выделил 4 проблемы, которые он может решить. Некоторые притянуты зауши, тем не менее…
Сложность
Основная проблема которую решает Compose это сложность. Очевидно что мы становимся очень привередливыми к UI. Обычные элементы нам уже кажутся скучными. Чтобы у продукта был коммерческий успех у него обязан быть классный UX и красивый дизайн. Градиенты, тени, сложные списки со сложной анимацией все это переходит в разряд обычного приложения и если этого нет, то мы начинаем чувствовать неудобство при использовании.
Все это приводит к страшной запутанности кода. У нас есть верстка в xml, есть Custom View которые описываются кодом. После мы должны написать код для фрагмента, далее поместить его в Activity, Activity в утку, утку в зайца ну вы поняли. У всех этих элементов есть свои ЖЦ и даже простое приложение с одной кнопкой требует кучу кода. Каждый раз тратим на кучу времени на болерплейт, который как кажется можно не писать.
В основе Compose лежит идея описания UI при помощи чистых функций. Ранее я делал посты, как чистые функции помогают сделать код проще, в запиненных сообщениях. Благодаря тому, что делаем все через код, это позволяет на лету заменять расположение элементов на экране. Не нужно теперь создавать свою кнопку если нужно в ней например разместить два TextView, теперь это все доступно их коробки.
Файл с версткой
С версткой какая проблема, мы создаем её через xml, который потом упаковывается в специальный бинарный формат для скорости. В момент когда нам нужно показать экран, нужный файл с версткой сначала подгружается из файловой системы, затем парсится и через рефлексию создаются нужные View. Только после этого они рассчитываются и отрисовываются на экране.
Это конечно уже максимально оптимизированный процесс, однако наличие рефлексии и подгрузки из файла говорит о том, что накладные расходы все же есть.
Состояние у View
В приложении обычно есть как минимум два слоя. UI слой и Presentation. Архитектура UI в Android построена таким образом, что у нее есть состояние. Это состояние чекбоксов, введенный текст, добавленные или удаленные View и т.д. В Presentation слое у нас тоже есть состояние. Основная проблема, что эти состояния нужно синхронизировать.
Это проводит к тому, что у нас логика управления состоянием размазывается. Какую-то часть сохраняем в Bundle, какую-то часть в Presentation. Со временем все это разрастается так, что хочется сменить профессию. Порой стараются не делать состояние в Presentation, и в итоге у нас состояние контролирует UI, что он делать не должен. Есть архитектуры вроде MVI которые сглаживают эту проблему, но не решают окончательно.
Не должно быть у UI своего состояние. Состояние должно быть только у Presentation слоя, который уже передает его UI. Compose именно так и построен. У Compose функций нет своего состояния, мы лишь указываем что хотим получать на экране в зависимости от изменения состояния Presentation.
⬇
Сложность
Основная проблема которую решает Compose это сложность. Очевидно что мы становимся очень привередливыми к UI. Обычные элементы нам уже кажутся скучными. Чтобы у продукта был коммерческий успех у него обязан быть классный UX и красивый дизайн. Градиенты, тени, сложные списки со сложной анимацией все это переходит в разряд обычного приложения и если этого нет, то мы начинаем чувствовать неудобство при использовании.
Все это приводит к страшной запутанности кода. У нас есть верстка в xml, есть Custom View которые описываются кодом. После мы должны написать код для фрагмента, далее поместить его в Activity, Activity в утку, утку в зайца ну вы поняли. У всех этих элементов есть свои ЖЦ и даже простое приложение с одной кнопкой требует кучу кода. Каждый раз тратим на кучу времени на болерплейт, который как кажется можно не писать.
В основе Compose лежит идея описания UI при помощи чистых функций. Ранее я делал посты, как чистые функции помогают сделать код проще, в запиненных сообщениях. Благодаря тому, что делаем все через код, это позволяет на лету заменять расположение элементов на экране. Не нужно теперь создавать свою кнопку если нужно в ней например разместить два TextView, теперь это все доступно их коробки.
Файл с версткой
С версткой какая проблема, мы создаем её через xml, который потом упаковывается в специальный бинарный формат для скорости. В момент когда нам нужно показать экран, нужный файл с версткой сначала подгружается из файловой системы, затем парсится и через рефлексию создаются нужные View. Только после этого они рассчитываются и отрисовываются на экране.
Это конечно уже максимально оптимизированный процесс, однако наличие рефлексии и подгрузки из файла говорит о том, что накладные расходы все же есть.
Состояние у View
В приложении обычно есть как минимум два слоя. UI слой и Presentation. Архитектура UI в Android построена таким образом, что у нее есть состояние. Это состояние чекбоксов, введенный текст, добавленные или удаленные View и т.д. В Presentation слое у нас тоже есть состояние. Основная проблема, что эти состояния нужно синхронизировать.
Это проводит к тому, что у нас логика управления состоянием размазывается. Какую-то часть сохраняем в Bundle, какую-то часть в Presentation. Со временем все это разрастается так, что хочется сменить профессию. Порой стараются не делать состояние в Presentation, и в итоге у нас состояние контролирует UI, что он делать не должен. Есть архитектуры вроде MVI которые сглаживают эту проблему, но не решают окончательно.
Не должно быть у UI своего состояние. Состояние должно быть только у Presentation слоя, который уже передает его UI. Compose именно так и построен. У Compose функций нет своего состояния, мы лишь указываем что хотим получать на экране в зависимости от изменения состояния Presentation.
⬇
🔥24👍2
Путаница с темами и стилями
Очень частая ошибка, особенно у начинающих. Не понятно чем стиль отличается от темы. Они буквально описываются одним и тем же атрибутом style. В любую View можно указать вместо темы стиль, а вместо стиля тему и никакой линтер нигде не скажет, что что-то не так. Разница описывается лишь неймингом в xml. Когда нет никаких ограничений, рано или поздно кто-то сотворит фигню.
Помимо этого порой сложно узнать какие атрибуты вообще тема устанавливает. Это происходит из архитектурного ограничения. Для стилей и тем хорошо подходит принцип наследования. Однако xml это вообще не тот язык, чтобы описывать наследование. Поэтому у тем и стилей достаточно костыльное наследование в xml. Нужно скакать по ссылкам в надежде, что ты доберешься до корневой темы, которая устанавливает нужные тебе атрибуты.
В compose эту проблему решили просто тем, что теперь тема или стиль это конкретный объект. Благодаря системе типизации их невозможно спутать, компилятор тупо такое не пропустит. Можно легко понять какие атрибуты у тебя установлены в теме, так как это конфигурируется специальной функцией.
Очень частая ошибка, особенно у начинающих. Не понятно чем стиль отличается от темы. Они буквально описываются одним и тем же атрибутом style. В любую View можно указать вместо темы стиль, а вместо стиля тему и никакой линтер нигде не скажет, что что-то не так. Разница описывается лишь неймингом в xml. Когда нет никаких ограничений, рано или поздно кто-то сотворит фигню.
Помимо этого порой сложно узнать какие атрибуты вообще тема устанавливает. Это происходит из архитектурного ограничения. Для стилей и тем хорошо подходит принцип наследования. Однако xml это вообще не тот язык, чтобы описывать наследование. Поэтому у тем и стилей достаточно костыльное наследование в xml. Нужно скакать по ссылкам в надежде, что ты доберешься до корневой темы, которая устанавливает нужные тебе атрибуты.
В compose эту проблему решили просто тем, что теперь тема или стиль это конкретный объект. Благодаря системе типизации их невозможно спутать, компилятор тупо такое не пропустит. Можно легко понять какие атрибуты у тебя установлены в теме, так как это конфигурируется специальной функцией.
🔥26👍3❤1