Dolgo.polo Dev | Денис Долгополов – Telegram
Dolgo.polo Dev | Денис Долгополов
2.27K subscribers
88 photos
2 videos
120 links
Разбираемся в мобильной разработке (Android/iOS) и пытаемся выяснить, зачем оно так устроено

Статистика/цены: @dolgo_polo_dev_stats

По вопросам/рекламе: @dolgopolovdenis
Download Telegram
​​Dependency Injection на пальцах (DI, Dagger, Hilt, Koin)

Долго боялся лезть разбираться в том, что же такое внедрение зависимостей. Но оказалось все очень просто - все библиотеки делают одно и то же:
Выносят создание класса в отдельный файл

Если раньше вы писали: SomeManager variable = new SomeManager();

прямо там, где вам нужен был этот менеджер, то теперь будете писать: @Inject SomeManager variable;

а для создания класса SomeManager напишите одну функцию, которая будет вызываться автоматически:
SomeManager provide() { return new SomeManager(); }

Зачем?
1. Чтобы заменив в одном месте параметры создания класса, сразу заменить их везде
2. Чтобы каждый раз не писать new SomeManager(), достаточно аннотации
3. Чтобы легко заменить класс SomeManager на SomeManager2, внеся изменения только в одном месте
4. Чтобы легче было отслеживать жизненный цикл этого объекта (большинство библиотек умеют создавать\уничтожать объект самостоятельно, привязываясь к жизненному циклу класса, в который был инъецирован объект)
5. Чтобы управлять количеством созданных копий. Например, если SomeManager используется в десяти разных местах одной активити, то можно с помощью одной аннотации указать, что во всех этих местах должен использоваться один и тот же экземпляр SomeManager, а не создаваться новые. А вот в другой активити уже будет создан независимый экземпляр SomeManager. И вам не придется следить за этим самостоятельно

какие еще плюшки от DI можно получить и какие минусы он тащит за собой?
👍5
.aab (.AAB) — что и зачем

как и .apk, новый формат .aab - это архив, содержащий все файлы (код, ресурсы) приложения

разница следующая:

— если вы отдаете в плей маркет .apk, то пользователю придется скачать этот файл со всеми ресурсами, включая те, которые на его устройстве использоваться не будут

например, вы в ресурсы положили картинку в шести разрешениях (mdpi, hdpi, xhdpi...). а у пользователя разрешение hdpi, но скачать ему придется все 6 картинок, и только потом телефон сможет удалить лишнее

— если отдаете .aab, то гугл, перед тем как отдать пользователю apk, сам выкинет лишние картинки, и отдаст только нужное

эта оптимизация позволяет уменьшить вес АПК для пользователя на 8-50%

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


теперь вопрос к залу: почему гугл не может просто вырезать лишние ресурсы из апшки?
👍12
​​Да кто этот ваш Context

Самая загадочная и покрытая тайнами, как говорит молодежь, дичь в андроиде — Context Используется на каждом шагу, а с лету написать определение не так уж просто

Но если не ударяться в философию, то Context - это класс, который обеспечивает доступ из одной части приложения (кода) к другой (ресурсам).
Получив context, вы заполучаете методы для считывания картинок, строковых ресурсов и всего остального, запиханного в ваш apk

А еще кто-то сильно подумал и решил, что он же будет отвечать за навигацию (запуск активитей) и работу фоновых служб (сервисов и бродкастов), то есть за взаимодействие приложения с системой

Позже кто-то не расслышал про "ни при каких условиях не создавать God-классы" и назначил контекст ответственным за работу с SharedPreference. Ну и за коннект со внутренними и внешними файлами заодно

Но в целом есть короткий ответ: context - прокладка между кодом приложения и остальной системой

Вопрос с собеса на джуна: почему нельзя передавать контекст, полученный методами getContext()\getActivity\Activity.this куда попало?
👍6
​​Волшебство LiveData

LiveData - это класс, работающий по принципу Observable.
Поэтому сначала вспомним, какими функциями занимается Observable-класс:

1. Хранит объект какого-то типа. Например, Observable<Integer> - хранит объект типа Integer
2. Может получать новый объект. Например, вот так: Observable .post(123) //готово, мы отдали на хранение новый объект типа Integer
3. Может отдавать всем подписчикам хранимый объект. Подписаться на такой Observable можно в любом месте, вызвав Observable.observe()

Обычно Observable нужен для такого сценария:

1. Создаем новый Observable
2. Подписываемся на него в десяти местах
3. Отдаем Observable новое значение на хранение
4. Observable передает в эти десять мест полученное значение

То есть мы снимаем с себя головную боль по поводу того, как из одного места прокинуть новое значение в десять мест

А теперь пару строк, ради которых вы дочитали предисловие:

LiveData - прокаченный Observable. Его крутость в том, что он следит за жизненным циклом подписчиков, и отсылает данные только живым подписчикам.

Например, подписались мы на Observable в активити. Через пять минут в Observable поступили новые данные, он их отправляет в активити — а она то сдохла!

И приложение падает. А LiveData этого бы не сделала.
LiveData отправляет данные, только если подписанный класс находится в состоянии onStart() или onResume(). В противном случае просто хранит данные до тех пор, пока подписанный класс не проснется или не появится новый подписчик
🔥1
​​Главная цель архитектуры или зачем столько страданий

Можно выкупить принципы всех архитектур, так и не поняв, зачем столько геморроя © Аристотель, 300кк век до н. э.

Маленькие приложения могут прожить без архитектуры. Первые 5-7 приложений я написал, совершенно не задумываясь о потребности в строгом паттерне. Каждая статья про архитектуру мобильного приложения рождала мысль: "И зачем это усложнение - будем писать в три раза больше кода, чтобы код стал понятнее, вы серьезно?"

А потом начались проекты c десятками тысяч строк. И мне показалось, что понял, зачем нужны MVC, MVVM, MVI, MVP... "Чтобы разносить код приложения в разные классы и модули! Чтобы знать, где что лежит, не открывая файл! Чтобы не плодить God-objects!" - воскликнул наивно

На самом деле нет

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

Основная цель любой архитектуры — обеспечить однонаправленный поток данных

Каждый из подходов позволяет сделать так, чтобы у нас был всего один поток данных: из недр приложения (источника данных like база данных\сервер) к пользователю.
При этом в видимой части приложения (UI) могут родиться только события, которые пойдут параллельно данным, но в обратную сторону

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

Теперь можно выбирать на практике самую близкую душе архитектуру, понимая их философское значение
👍8
​​Каналы уведомлений

Начиная с версии Android 8 (API 26), перед тем как вывести любое уведомление в шторку, нужно создать Notification Channel (далее - канал), а после этого указать channelId при создании объекта Notification (собстна, самого уведомления)

Каналы нужны для того, чтобы пользователь мог в настройках (Приложения -> О приложении -> Уведомления) отключить только ненужные сообщения от приложения

И это чертовски удобно. Например, я отключил все каналы от Вконтакте, кроме тех, что отвечают за оповещение о новых сообщениях

Поэтому, работая с уведомлениями, нужно продумать, на какие логические блоки можно разбить все ваши нотификации, и дать каналам понятные пользователю имена
​​Общая ViewModel

Не самая очевидная вещь: один экземпляр ViewModel может быть доступен из нескольких фрагментов или активитей

Например, вам нужна аватарка пользователя в двух фрагментах

Вы можете:
— для каждого фрагмента создать свою ViewModel и прописать в них обоих одинаковую логику получения аватарки. При этом в каждой из этих вьюМоделей будет своя MutableLiveData<Bitmap>, хранящая картинку

— создать одну вьюМодел и получить к ней доступ из обоих фрагментов

Фокус в том, что вьюМодел хранится у кого-то. И этот кто-то - ViewModelStoreOwner. Владельцем (Owner) могут быть:
- фрагменты
- активити
- кастомные классы, реализующие интерфейс ViewModelStoreOwner (редкий кейс)

Хранитель вьюМодели определяется во время получения ее экземпляра через ViewModelStoreProvider(ViewModelStoreOwner owner)

Так вот, если в качестве владельца передать не сам фрагмент, а его родительскую активити, то хранить экземпляр вьюМодел будет активити. А доступ к этой активити есть и у второго фрагмента из нашего примера. Значит, когда он захочет получить вьюМодель через ViewModelStoreProvider(getActvivityy()), то он получит тот же экземпляр вьюМодели, с которым работал первый фрагмент (то есть там уже будет лежать аватарка)

Знать об этом способе стоит, но хороший ли это подход с точки зрения архитектуры и памяти?
Style vs Theme

Если вы копируете код, чтобы вставить его в другом месте, задумайтесь. Что-то вы делаете не так

Это касается и xml-разметки. Одинаковые параметры вьюшек нужно выносить в файл styles.xml и ссылаться на них с помощью android:theme="" или style=""

Сначала их одинаковые черты:

и theme, и style — мапа (набор ключ-значений), где:
• ключ - название атрибута
• значение - цвет, размер, форма, тип (например, inputType) или ссылка на файл с ресурсами, где лежит конкретное значение

Теперь различие:

• Style — перечисленные атрибуты применяются только к той вью, к которой применен style

• Theme — перечисленные атрибуты применяются не только к той вью, к которой применена тема, но и ко всем её дочерним вьюшками

Например, если вы примените theme на уровне приложение (укажите в манифесте), то все активити, вьюГруппы и вьюшки будут брать значения оттуда

Если примените theme к ViewGroup (Linear, Relative...), то и сама вьюГруппа, и все её наследники будут брать значения оттуда. При этом все древо родителей об этих значениях не узнают

когда лучше использовать одно, а когда второе?
👍5
​​minSdkVersion vs targetSdkVersion

В файле build.gradle уровня приложения можно указать оба значения. Разница в них следующая:

• minSdkVersion — указывает минимальную версию Android (а точнее version Android SDK Platform API), на которую можно будет установить приложение

Например, при разработке вы заметили, что используете функцию, которая появилась только с Android API 20. Если не установить minSdkVersion = 20, то у пользователей на более ранних версиях приложение будет падать на месте вызове несуществующей в их версии функции

targetSdkVersion — указывает версию Android SDK, фишки который вы, как разработчик, полностью оттестировали

Например, в версии 23 (Android 6.0) переработали систему разрешений (permissions): раньше все разрешения приложение должно было запросить в процессе установки. Начиная с SDK 23, разрешения запрашиваются во время работы приложения

Получается, все приложения, не обновленные разработчиками после выхода Android 6.0, должны падать с ошибкой Runtime Permission, так как во время выполнения приложения разрешения в коде не проверяются?

Нет, падать они не будут. В них установлен targetSdkVersion < 23, а это значит, что система сделает для них исключение и даст им работать по старым правилам (то есть им будет достаточно получить разрешения во время установки приложения)

....

Если с этими все понятно, то что такое maxSdkVersion и compileSdkVersion?

Почему стоит следить за обновлением targetSdkVersion или почему не стоит оставаться на старых версиях?
👍1
​​Начинаем выполнять ТЗ в четыре раза быстрее, а в освободившееся время чилим

Рутинный код пишется в несколько раз быстрее, если использовать следующие хот-кеи (о существовании которых почему-то студия не рассказывает большими красными буквами при первом старте):

Alt + Enter — вызывает бесполезное контекстное меню. но пару функций в нём маст хев:

• если вызвать меню, поставив курсор на строку, то первым пунктом будет Extract string resource (позволяет переместить строку в файл strings. xml, где ей и место)
• если вызвать на приватном поле, то предложит сгенерировать геттер и сеттер
• если вызвать на фигурных скобках, в которые обернута только одна строка, то сможете быстро удалить эти фигурные скобки

выделить код + Ctrl + Alt + V — создает переменную и присваивает ей выделенное значение

выделить код + Ctrl + Alt + M — создать функцию и вынести в неё выделенные строки кода

Shift + F6 — переименовать переменную, функцию, класс... вызвать можно в любом месте, студия переименует элемент во всех местах

Ctrl + Alt + O — удалить все неиспользуемые импорты из текущего файла

Ctrl + O — показать список методов, которые можно имплементировать или переопределить

Ctrl + клик мыши — переход к исходникам (к классу, функции, переменной...)

Ctrl + F — поиск по файлу

двойное нажатие Shift — поиск по всему проекту (поиск файлов, классов, функций...)

....

какими ещё хоткеями вы экономике себе время на дополнительную серию сериальчика?
👍4
Количество разработчиков и просто отличных ребят в этом чатике перевалило за первую сотню

Всем пива за свой счет! 🍻
Задумайтесь, когда мы еще соберемся такой хорошей компанией? Повод будет, когда приблизимся к пятистам подписчикам, а это ой как ни скоро

Что в ближайших планах:

• поднять количество постов — едва ли, а вот качество в приоритете

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

когда оно случится - не знаю, но все будет
​​Hash\хэш-код\хэш-функция\хэширование

Если вы понимаете, что такое хэш, то гарантированно знаете, как работают: хэш-функции, хэш-таблицы, хэш-мапы, сравнения

Хэш — число, которое однозначно идентифицирует объект, опираясь на заранее придуманные правила сравнения (вольная интерпретация автора, в википедии другие слова)

• хэшем может быть id, если мы заранее договоримся о том, что два объекта точно равны, если равны их id

• хэшем может быть поле name (или любое другое поле), приведенное к числу, если договоримся, что два объекта точно равны, если равны их поля name

• хэшем может быть timestamp создания объекта, сумма всех его полей... почти всё что угодно)

Главное — решить, что в вашей бизнес-логике "равно". Чаще всего "равно" = "у обоих объектов все поля равны" или "равно" = "у обоих объектов равны поля id"

...

В Java у всех классов есть функция hashCode (определена в родительском классе Object). Что для нее "равно"? (для каких объектов вернет одинаковых хэш)

Чем отличается hashCode в Kotlin у классов, помеченных как data class

Проблема коллизий хэш-функции — о чем она и чем опасна?
​​Где стоит создавать слушатели для элементов RecyclerView — onCreateViewHolder vs onBindViewHolder

У каждого ViewHolder может быть одна или несколько кнопок, на которых будут висеть листенеры (onClick, onTouch, а может и что-то потяжелее)

Пойдём от противного — почему не стоит выставлять слушатели в onBindViewHolder:

• как известно, в onBindViewHolder приходит holder, в котором лежит view, которая переиспользуется для разных элементов списка. получается, вы каждый раз будете создавать новый объект листенера и вешать его на вьюху, у которой уже есть листенер. создаем лишний объект — теряем память, теряем время

• в onBindViewHolder приходит position, которая есть у холдера в момент байндинга. А что если элемент будет перемещен, допустим, в следствие Drag-and-drop от пользователя?
onBindViewHolder заново вызван не будет, а position изменилось

значит, правильно создавать и вешать слушатель в onCreateViewHolder, получая position через holder.getAdapterPosition() внутри слушателя

также стоит проверять getAdapterPosition() != RecyclerView.NOPOSITION:

position = -1 = NO
POSITION, когда RecyclerView проводит перерасчет позиции холдера (в связи с перемещением\удалением\смещением)

....

также существуют методы:

• getAbsoluteAdapterPosition()
• getBindingAdapterPosition()
• getLayoutPosition()
• getPosition()

в чем между ними разница?
👍5
​​Handler & Looper

Когда речь заходит о многопоточности, почти все статьи забывают рассказать о том, как главному потоку (aka main thread) удается не заканчиваться

Ведь любой Thread выполняет метод run() и после этого останавливается! MainThread не исключение!

Значит, в методе run главного потока крутится бесконечный while(true), в теле которого раз за разом проверяются все события, происходит отрисовка графики и прочие штуки, которые мы выполняем в главном потоке

Так оно и есть, только немного (много) сложнее и скрыто за абстракциями:

Looper (Looper.prepare(), Looper.loop(), Looper.quit()) — создает в методе run бесконечный цикл, который не позволяет потоку "закончиться"

MessageQueue — очередь, в которую складываются приходящие команды. На каждой итерации бесконечного цикла из очереди будут выниматься команды и выполняться

Handler — забирает команду (Message) из вашего кода и кладет ее в MessageQueue

вы можете закольцевать любой поток, использовав эти механизмы

....

сколько циклов в секунду должен успевать делать Looper главного потока, чтобы приложением можно было пользоваться с комфортом (без просадки кадров в секунду)?
👍3
background / backgroundTint / backgroundTintMode vs tint / tintMode

Овладев этими параметрами View, вы перестанете хранить несколько экземпляров иконок, покрашенных в разные цвета, и сэкономите время на создании одинаковых по форме, но отличающихся по цвету drawable-ресурсов

• android:background = цвет, картинка, иконка, фигура или селектор, описанный в xml-файле

• android:backgroundTint = цвет, который будет накладываться на background

• android:backgroundTintMode = способ смешения цветов

Например, выбрав tintMode, можно определить, будут ли смешиваться альфа-каналы (прозрачность) заданного цвета и ресурса из background.
Также можно задать tintMode, который позволит полностью игнорировать исходный цвет ресурса, заданного в поле background.

Но перекрашивать можно не только background. Например, можно менять цвет ресурса, заданного атрибутом android:src

В этом случае нам понадобятся:

android:tint — цвет, который будет накладываться на src
android:tintMode — способ смешения цветов

Если минимальная версия sdk не позволяет использовать эти атрибуты, можно установить их через библиотеку AppCompat — достаточно заменить префикс android на app (например, app:tintMode)

Вывод: не стоит дублировать ресурсы одной формы — можно хранить их в одном экземпляре, например, черного цвета, а там где нужно — перекрашивать его
👍4
​​MVVM — архитектура за 100 слов
(Model - View - ViewModel)

Расширение поста — https://news.1rj.ru/str/dolgo_polo_dev/22

View — умеет только отображать данные, которые приходят из ViewModel

Обычно View - это Activity или Fragment

View подписывается на LiveData-объекты, которые лежат во ViewModel — когда данные приходят, она их отображает (никак не обрабатывая!)

View передает события (Actions - клики, тачи, вводы тексты...) во ViewModel

Model — на самом деле Model Source (источник данных, Repository или же Database)

умеет принимать запрос на данные от ViewModel, получать их (с сервера или локального хранилища) и отдавать обратно во ViewModel

ViewModel — прослойка между View и Model. Умеет:
• принимать события (Actions) от View и реагировать на них
• запрашивать данные у Model
• делать бизнес-логику - обрабатывать данные, трансформировать и соединять их
• отдавать данные во View через объекты LiveData (MutableLiveData)
👍2
​​Как воруется платный контент

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

Не стоит:
• хранить информацию о купленном контенте локально
• проверять доступ к контенту исключительно отдельным запросом к серверу

Почему не стоит или как проводится атака:

1) из установленного приложения вытаскивается apk-архив

2) dex-файлы с кодом перегоняются в smali-код
smali код более читабельный для человека, чем dex

3) в коде находится строчка, в которой проверяется доступ к платному контенту
то есть строчка с if(userHasAccess())
и меняется на строчку if(true) или просто удаляется

4) измененный код собирается обратно в apk-файл

5) новый apk подписывается и устанавливается на устройство
а также рассылается всем друзьям)

Как избежать:
единственный известный мне способ — пнуть бек-разработчика, чтобы он на своей стороне всегда проверял, можно ли этому пользователю отдать контент на запрос от приложения

сервер должен относиться к приложению как к штуке, которая может попытаться его наебать

например, если от приложения приходит запрос на прослушивание аудиокниги, то сервер должен проверить, купил ли пользователь эту книгу

....

о каких еще способах воровства вам известно?
👍3
​​AudioFocus — как правильно издать звук

Мы этого практически не замечаем, но в телефоне достаточно много типов свистоперделок — музыка, уведомления, звонки, будильник, короткие звуки в приложениях (редко, но иногда анимации в приложениях озвучивают), фоновая музыка в играх и звуки, связанные с событиями в игре

Весь этот музыкальный мусор может воспроизводиться параллельно, последовательно или асинхронно

Тут введем понятие "фокус" (AudioFocus) — право приложения перетащить большую часть аудио-внимания на себя


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

• замолчать - выключите свою музыку, пускай заиграет другое приложение
например, пользователь открыл видео на ютубу - мы ему больше не нужны

• приглушиться - ваша музыка продолжит играть, но тише.
например, пришло уведомление - нашу музыку приглушаем на секунду, чтобы пользователь услышал "дзынь" от пуша

• забить - пускай пользователь слушает оба потока одновременно, его проблемы

• замолчать, но автоматически вернуться
например, пользователь начал слушать голосовое в вк - выключаем нашу музыку, а как голосовое закончится - включаемся заново


все эти махинации разруливаются через AudioManager.OnAudioFocusChangeListener, в который система будет кидать флаг состояние фокуса

....

это единственная область в андроиде, где приложения могут поднасрать друг другу. ведите себя культурно)
👍6
Карта канала

t.me/dolgo_polo_dev/14 — Негласные правила в названиях
t.me/dolgo_polo_dev/15 — Отличие onStart() от onResume()
t.me/dolgo_polo_dev/18 — Dependency Injection на пальцах (DI, Dagger, Hilt, Koin)
t.me/dolgo_polo_dev/19 — .aab (.AAB) - что и зачем
t.me/dolgo_polo_dev/20 — Да кто этот ваш Context
t.me/dolgo_polo_dev/21 — Волшебство LiveData
t.me/dolgo_polo_dev/22 — Главная цель архитектуры или зачем столько страданий
t.me/dolgo_polo_dev/23 — Каналы уведомлений
t.me/dolgo_polo_dev/24 — Общая ViewModel
t.me/dolgo_polo_dev/25 — Style vs Theme
t.me/dolgo_polo_dev/26 — minSdkVersion vs targetSdkVersion
t.me/dolgo_polo_dev/27 — Начинаем выполнять ТЗ в четыре раза быстрее, а в освободившееся время чилим
t.me/dolgo_polo_dev/29 — Hash\хэш-код\хэш-функция\хэширование
t.me/dolgo_polo_dev/30 — Где стоит создавать слушатели для элементов RecyclerView - onCreateViewHolder vs onBindViewHolder
t.me/dolgo_polo_dev/31 — Handler & Looper
t.me/dolgo_polo_dev/32 — background / backgroundTint / backgroundTintMode vs tint / tintMode
t.me/dolgo_polo_dev/33 — MVVM - архитектура за 100 слов
t.me/dolgo_polo_dev/34 — Как воруется платный контент
t.me/dolgo_polo_dev/35 — AudioFocus - как правильно издать звук
t.me/dolgo_polo_dev/39 — Editable vs String
t.me/dolgo_polo_dev/43 — Где хранится Bundle во время уничтожения / Когда работает onSaveInstanceState()
t.me/dolgo_polo_dev/45 — Package Name != Application ID
t.me/dolgo_polo_dev/47 — Как по hashCode определяется позиция
t.me/dolgo_polo_dev/48 — Когда не нужно заменять HashMap на ArrayMap
t.me/dolgo_polo_dev/50 — Концепция InputStream - Reader - Buffered
t.me/dolgo_polo_dev/51 — Kotlin Coroutine. Suspend - зачем и как?
t.me/dolgo_polo_dev/52 — sealed interface vs sealed class vs enum
t.me/dolgo_polo_dev/55 — Kotlin Coroutine — корутины за 100 слов
t.me/dolgo_polo_dev/57 — Типы permissions для получения локации
t.me/dolgo_polo_dev/59 — Философия LayoutParams
t.me/dolgo_polo_dev/60 — Xml - зачем префиксы android, tools, app
• Kotlin Operator
• Как придумать свои единицы измерения на примере Compose
Task или виды Activity launch mode
• Порядок инициализации полей, конструкторов и блоков
• MVI — архитектура за 100 слов
• URL vs URI
• Kotlin Operator

Рубрика #очевидные_вещи

t.me/dolgo_polo_dev/40 - правила рубрики
t.me/dolgo_polo_dev/42 - Java vs Kotlin

Чтобы поделиться постом, кликните по нему ПКМ и выберите Copy Post Link
Или старый добрый Select Message -> Forward
👍3🔥21
Dolgo.polo Dev | Денис Долгополов pinned «Карта канала • t.me/dolgo_polo_dev/14 — Негласные правила в названиях • t.me/dolgo_polo_dev/15 — Отличие onStart() от onResume() • t.me/dolgo_polo_dev/18 — Dependency Injection на пальцах (DI, Dagger, Hilt, Koin) • t.me/dolgo_polo_dev/19 — .aab (.AAB) -…»
​​Editable vs String

• String - класс из Java
• Editable - интерфейс из Android SDK

• String - неизменяем в размере (все операции, меняющие размер строки, на самом деле создают новую строку)
• Editable - позволяет работать с массивом символов динамической длины

• String - хранит только символы
• Editable - хранит символы и их разметку (цвет, размер, стиль ≈ Spannable)

Editable активно используют EditText и TextView

Точнее в них используется имплементация Editable — SpannableStringBuilder, которая помимо всего прочего обеспечивает взаимодействие с интерфейсом TextWatcher (afterTextChanged() - beforeTextChanged() - onTextChanged())

....

бываю ли кейсы, когда стоит самому имплементировать Editable или хотя бы использовать его в местах, не связанных с EditText\TextView?