Негласные правила в названиях
Называть классы можно как угодно, но со временем каждый разработчик приходит к одним и тем же ключевым словам, которые на удивление очень точно описывают содержание файла:
Base -> класс, описывающий переменные и функции, которые понадобятся всем его наследникам. Причем это настолько удобно, что при открытии пустого проекта первым делом создаю классы вроде BaseFragment, BaseActivity, BaseRepository... и унаследуюсь от них, просто на всякий случай
Utils -> штука, которая делает какие-нибудь технические операции над данными. Например, конвертируем таймстамп в дату
Manager -> сюда скидываем все, что непонятно куда девать еще
Api (обычно), Service (реже) -> описание запросов к серверу
Repository, Data, Container, Source -> источник данных
Listener, Callback -> класс, который описывает функции, которые будут вызываться при возникновении события
какие еще штампы забыл?
Называть классы можно как угодно, но со временем каждый разработчик приходит к одним и тем же ключевым словам, которые на удивление очень точно описывают содержание файла:
Base -> класс, описывающий переменные и функции, которые понадобятся всем его наследникам. Причем это настолько удобно, что при открытии пустого проекта первым делом создаю классы вроде BaseFragment, BaseActivity, BaseRepository... и унаследуюсь от них, просто на всякий случай
Utils -> штука, которая делает какие-нибудь технические операции над данными. Например, конвертируем таймстамп в дату
Manager -> сюда скидываем все, что непонятно куда девать еще
Api (обычно), Service (реже) -> описание запросов к серверу
Repository, Data, Container, Source -> источник данных
Listener, Callback -> класс, который описывает функции, которые будут вызываться при возникновении события
какие еще штампы забыл?
👍5
Отличие onStart() от onResume()
onStart - вызывается, когда вьюшка (активити\фрагмент) выводится на экран. при этом вьюшка может быть чем-та перекрыта, например, всплывающим диалогом или шторкой.
пользовать видит часть экрана, но пользоваться им не может (т.к. ему нужно сначала закрыть диалог\убрать шторку)
onResume - вызывается, когда у пользователя открыт и ничем не перекрыт экран, то есть он может с ним беспрепятственно взаимодействовать
p.s. при этом onStart будет вызван перед onResume, даже если экран не перекрыт. но полный контакт с пользователем есть только после onResume
onStart - вызывается, когда вьюшка (активити\фрагмент) выводится на экран. при этом вьюшка может быть чем-та перекрыта, например, всплывающим диалогом или шторкой.
пользовать видит часть экрана, но пользоваться им не может (т.к. ему нужно сначала закрыть диалог\убрать шторку)
onResume - вызывается, когда у пользователя открыт и ничем не перекрыт экран, то есть он может с ним беспрепятственно взаимодействовать
p.s. при этом onStart будет вызван перед onResume, даже если экран не перекрыт. но полный контакт с пользователем есть только после onResume
👍4
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 можно получить и какие минусы он тащит за собой?
Долго боялся лезть разбираться в том, что же такое внедрение зависимостей. Но оказалось все очень просто - все библиотеки делают одно и то же:
Выносят создание класса в отдельный файл
Если раньше вы писали: 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%
а степень уменьшения веса зависит от количества ресурсов, дублирующих друг друга под разные устройства
теперь вопрос к залу: почему гугл не может просто вырезать лишние ресурсы из апшки?
как и .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 куда попало?
Самая загадочная и покрытая тайнами, как говорит молодежь, дичь в андроиде — 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(). В противном случае просто хранит данные до тех пор, пока подписанный класс не проснется или не появится новый подписчик
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) могут родиться только события, которые пойдут параллельно данным, но в обратную сторону
Архитектуры спасают нас от ветвления, запрещают нам создавать и изменять данные вне определенного потока, чтобы мы всегда могли пройти по хлебным крошкам и найти, на каком этапе пути (а не макаронного дерева) произошла ошибка
Теперь можно выбирать на практике самую близкую душе архитектуру, понимая их философское значение
Можно выкупить принципы всех архитектур, так и не поняв, зачем столько геморроя © Аристотель, 300кк век до н. э.
Маленькие приложения могут прожить без архитектуры. Первые 5-7 приложений я написал, совершенно не задумываясь о потребности в строгом паттерне. Каждая статья про архитектуру мобильного приложения рождала мысль: "И зачем это усложнение - будем писать в три раза больше кода, чтобы код стал понятнее, вы серьезно?"
А потом начались проекты c десятками тысяч строк. И мне показалось, что понял, зачем нужны MVC, MVVM, MVI, MVP... "Чтобы разносить код приложения в разные классы и модули! Чтобы знать, где что лежит, не открывая файл! Чтобы не плодить God-objects!" - воскликнул наивно
На самом деле нет
Все эти преимущества можно обеспечить и без архитектуры. Создавай кучу классов, придерживайся правил составления имен, — готово
Основная цель любой архитектуры — обеспечить однонаправленный поток данных
Каждый из подходов позволяет сделать так, чтобы у нас был всего один поток данных: из недр приложения (источника данных like база данных\сервер) к пользователю.
При этом в видимой части приложения (UI) могут родиться только события, которые пойдут параллельно данным, но в обратную сторону
Архитектуры спасают нас от ветвления, запрещают нам создавать и изменять данные вне определенного потока, чтобы мы всегда могли пройти по хлебным крошкам и найти, на каком этапе пути (а не макаронного дерева) произошла ошибка
Теперь можно выбирать на практике самую близкую душе архитектуру, понимая их философское значение
👍8
Каналы уведомлений
Начиная с версии Android 8 (API 26), перед тем как вывести любое уведомление в шторку, нужно создать Notification Channel (далее - канал), а после этого указать channelId при создании объекта Notification (собстна, самого уведомления)
Каналы нужны для того, чтобы пользователь мог в настройках (Приложения -> О приложении -> Уведомления) отключить только ненужные сообщения от приложения
И это чертовски удобно. Например, я отключил все каналы от Вконтакте, кроме тех, что отвечают за оповещение о новых сообщениях
Поэтому, работая с уведомлениями, нужно продумать, на какие логические блоки можно разбить все ваши нотификации, и дать каналам понятные пользователю имена
Начиная с версии Android 8 (API 26), перед тем как вывести любое уведомление в шторку, нужно создать Notification Channel (далее - канал), а после этого указать channelId при создании объекта Notification (собстна, самого уведомления)
Каналы нужны для того, чтобы пользователь мог в настройках (Приложения -> О приложении -> Уведомления) отключить только ненужные сообщения от приложения
И это чертовски удобно. Например, я отключил все каналы от Вконтакте, кроме тех, что отвечают за оповещение о новых сообщениях
Поэтому, работая с уведомлениями, нужно продумать, на какие логические блоки можно разбить все ваши нотификации, и дать каналам понятные пользователю имена
Общая ViewModel
Не самая очевидная вещь: один экземпляр ViewModel может быть доступен из нескольких фрагментов или активитей
Например, вам нужна аватарка пользователя в двух фрагментах
Вы можете:
— для каждого фрагмента создать свою ViewModel и прописать в них обоих одинаковую логику получения аватарки. При этом в каждой из этих вьюМоделей будет своя MutableLiveData<Bitmap>, хранящая картинку
— создать одну вьюМодел и получить к ней доступ из обоих фрагментов
Фокус в том, что вьюМодел хранится у кого-то. И этот кто-то - ViewModelStoreOwner. Владельцем (Owner) могут быть:
- фрагменты
- активити
- кастомные классы, реализующие интерфейс ViewModelStoreOwner (редкий кейс)
Хранитель вьюМодели определяется во время получения ее экземпляра через ViewModelStoreProvider(ViewModelStoreOwner owner)
Так вот, если в качестве владельца передать не сам фрагмент, а его родительскую активити, то хранить экземпляр вьюМодел будет активити. А доступ к этой активити есть и у второго фрагмента из нашего примера. Значит, когда он захочет получить вьюМодель через ViewModelStoreProvider(getActvivityy()), то он получит тот же экземпляр вьюМодели, с которым работал первый фрагмент (то есть там уже будет лежать аватарка)
Знать об этом способе стоит, но хороший ли это подход с точки зрения архитектуры и памяти?
Не самая очевидная вещь: один экземпляр 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...), то и сама вьюГруппа, и все её наследники будут брать значения оттуда. При этом все древо родителей об этих значениях не узнают
когда лучше использовать одно, а когда второе?
Если вы копируете код, чтобы вставить его в другом месте, задумайтесь. Что-то вы делаете не так
Это касается и 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 или почему не стоит оставаться на старых версиях?
В файле 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 — поиск по всему проекту (поиск файлов, классов, функций...)
....
какими ещё хоткеями вы экономике себе время на дополнительную серию сериальчика?
Рутинный код пишется в несколько раз быстрее, если использовать следующие хот-кеи (о существовании которых почему-то студия не рассказывает большими красными буквами при первом старте):
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
Проблема коллизий хэш-функции — о чем она и чем опасна?
Если вы понимаете, что такое хэш, то гарантированно знаете, как работают: хэш-функции, хэш-таблицы, хэш-мапы, сравнения
Хэш — число, которое однозначно идентифицирует объект, опираясь на заранее придуманные правила сравнения (вольная интерпретация автора, в википедии другие слова)
• хэшем может быть id, если мы заранее договоримся о том, что два объекта точно равны, если равны их id
• хэшем может быть поле name (или любое другое поле), приведенное к числу, если договоримся, что два объекта точно равны, если равны их поля name
• хэшем может быть timestamp создания объекта, сумма всех его полей... почти всё что угодно)
Главное — решить, что в вашей бизнес-логике "равно". Чаще всего "равно" = "у обоих объектов все поля равны" или "равно" = "у обоих объектов равны поля id"
...
В Java у всех классов есть функция hashCode (определена в родительском классе Object). Что для нее "равно"? (для каких объектов вернет одинаковых хэш)
Чем отличается hashCode в Kotlin у классов, помеченных как data class
Проблема коллизий хэш-функции — о чем она и чем опасна?
